C# Generic Events
Solution 1
It sounds like what you need is an interface type, rather than a delegate. Interface methods can accept open generic types (which is what you're after), even though delegates cannot. For example, one could define something like:
interface ActOnConstrainedThing<CT1,CT2>
{
void Act<MainType>(MainType param) where MainType: CT1,CT2;
}
Even if implementers of CT1
and CT2
do not share a common base type which also implements CT1
and CT2
, an implementation of Act
may use its passed-in parameter as a CT1
or CT2
without typecast, and could even pass it to routines which expect a generic parameter with CT1
and CT2
constraints. Such a thing would not be possible with delegates.
Note that using interfaces rather than delegates means that one cannot use the normal "event" mechanism and syntax. Instead, the object which would be an event publisher must maintain a list of object instances that implement the desired interface (e.g. a List<ActOnConstrainedThing<IThis,IThat>>
) , and enumerate the instances on that list (perhaps using foreeach
). For example:
List<IActOnConstrainedThing<IThis,IThat>> _ActOnThingSubscribers; void ActOnThings<T>(T param) where T:IThis,IThat { foreach(var thing in _ActOnThingSubscribers) { thing.Act<T>(param); } }
Edit/Addendum
The place where I employed this pattern also had some other stuff that didn't seem overly relevant to the question, which by my interpretation was asking how one can have a delegate (or equivalent) with an open type parameter, so that the object invoking the delegate-equivalent can supply the type parameter, without the object supplying the delegate having to know it in advance. Most cases where this is useful involve generic constraints, but since that was apparently introducing confusion, here's an example that doesn't:
interface IShuffleFiveThings { void Shuffle<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5); } List<IShuffleFiveThings _ShuffleSubscribers; void ApplyShuffles<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5) { foreach(var shuffler in _ShuffleSubscribers) { thing.Shuffle(ref p1, ref p2, ref p3, ref p4, ref p5); } }
The IShuffleFiveThings.Shuffle<T>
method takes five parameters by ref
and does something with them (most likely permutes them in some fashion; perhaps permuting them randomly, or perhaps permuting some randomly while leaving others where they are. If one has a list IShuffleFiveThings
, the things in that list can be used efficiently, without boxing or Reflection, to manipulate any kind of thing (including both class types and value types). By contrast, if one were to use delegates:
delegate void ActOn5RefParameters(ref p1, ref p2, ref p3, ref p4, ref p5);
then because any particular delegate instance can only act upon a single parameter type supplied at its creation (unless it's an open delegate which is called only via Reflection), one would need to create a separate list of delegates for every type of object one wished to shuffle (yes, I know one would normally handle permutations by using an array of integer indices; I chose permutation as an operation because it's applicable to all object types, not because this particular method of permuting things is useful).
Note that because the type T
in IShuffleFiveThings
does not have any constraints, implementations won't be able to do much with it except by typecasting (which may introduce boxing). Adding constraints to such parameters makes them much more useful. While it would be possible to hard-code such constraints within the interface, that would limit the interface's usefulness to applications requiring those particular constraints. Making the constraints themselves generic avoids that restriction.
Solution 2
You're in essence creating an instance of that delegate. Instances need to have their generic types defined.
The definition of your delegate can contain T, but your instance needs to define which T.
Solution 3
Given this example:
public delegate void FooDelegate<T>(T value);
public class FooContainer
{
public event FooDelegate<T> FooEvent;
}
The compiler as in your example does not like the FooEvent
declaration because T
is not defined. However, changing FooContainer
to
public delegate void FooDelegate<T>(T value);
public class FooContainer<T>
{
public event FooDelegate<T> FooEvent;
}
And now the compiler is ok with this because whoever creates instances of FooContainer will now have to specify type T like so
FooContainer<string> fooContainer = new FooFooContainer<string>();
However you could also constrain T
to an interface like this.
public delegate void FooDelegate<T>(T value) where T : IFooValue;
public class FooContainer
{
public event FooDelegate<IFooValue> FooEvent;
protected void OnFooEvent(IFooValue value)
{
if (this.FooEvent != null)
this.FooEvent(value);
}
}
public interface IFooValue
{
string Name { get; set; }// just an example member
}
In this case, you can raise the event with types as long as they implement the interface IFooValue
.
Alex
Updated on June 09, 2022Comments
-
Alex almost 2 years
I have a delegate with a generic type as one of the parameters:
public delegate void UpdatedPropertyDelegate<T>( RemoteClient callingClient, ReplicableProperty<T> updatedProp, ReplicableObject relevantObject );
Now, I want a public event that can be subscribed to for other classes to use. Therefore, I did:
public event UpdatedPropertyDelegate<T> UpdatedProperty;
However, the compiler doesn't like that. I don't understand why T has to be specified here. Surely it's specified when I fire the event, i.e.:
if (UpdatedProperty != null) { UpdatedProperty(this, readProperty, ReplicableObjectBin.GetObjectByID(readProperty.OwnerID)); }
So, am I doing something simple wrong? Or is this a massive failure of understanding?
Thanks.
-
payo about 12 yearsOr be in a generic class that define
T
-
Alex about 12 yearsSo, is there any way to pass a generic parameter in an event (without specifying the type T at compile time, which seems to take away the point of a generic to me)?
-
Jean-Bernard Pellerin about 12 yearsTrue, in that case T is defined by the class instance, so the delegate instance is also defined, which holds with what I stated.
-
Jim about 12 yearsThis is interesting. In the OP question, would this interface be implemented by type that was defining his event or would this be implemented by the type being passed in the event? Basically, how would this be used in the OP solution?
-
supercat about 12 years@Jim: See above edit. One can't use events, but one can achieve similar effects with interfaces. The pattern used by
IObservable
/IObserver
may be a good alternative to using events. -
Jim about 12 years@supercate - Right I get the observer pattern here but wouldn't the enclosing type still have to define IThis, IThat type parameters or it itself also specify these same type parameters?
-
supercat about 12 years@Jim: There often isn't much point in having a void function take an unconstrained generic-typed parameter; I thought two was a good number of generic type parameters to use for a sample interface, though one could perfectly well use more or fewer. If one doesn't need exactly two constraints on one's generic, one can adjust the interface and sample code as appropriate.
-
payo about 12 years@supercat I don't see how this is any less defined than using events/delegates. I would be very interested in some more thorough code samples. upvote ready for the giving if you can elaborate. I have tried the code and the generic arguments feel just as defined as they do on the events. Thanks.
-
Jim about 12 years@payo - that where I am getting hung up. Failing to see how this solves the type parameter issue.
-
supercat about 12 years@payo: An instance of
ActOnThing<Foo,Bar>
can be called with any generic parameter which complies with constraintsFoo
andBar
, and its implementation can use the passed-in parameter as aFoo
orBar
without typecasting (if one didn't mind typecasting the parameter to use it as aFoo
orBar
, one could simply accept a parameter of typeObject
and not bother with generics). One does not have to know, when generating an instance of a type implementingActOnThing<Foo,Bar>
, what the type of the parameter will be. -
payo about 12 years@supercat In my test - the parameter is both Foo AND Bar, If I don't implement both, it doesn't compile. But this is slightly off topic from the OP's question - we are still defining all the types at compile time. To the OP -- the issue is still that generics must be defined as others have stated.
-
supercat about 12 years@payo: The type has to either be defined at compile time, or be passed in as a generic type parameter. For
ActOnThing<Foo,Bar>.Act<T>
to be called, someone, somewhere, has to have a concrete type that is known at compile time and implements bothFoo
andBar
, but the routine which callsActOnThing<Foo,Bar>.Act<T>
doesn't have to know the type at compile time. For example,ActOnThings<T>
doesn't know at compile time what type it is going to be asked to pass to the instances ofActOnThing<IThis,IThat>
stored in its list. -
payo about 12 yearsYou are missing the point. Our issue with your answer was that you haven't shown the argument can be IThis OR IThat, it must be both in your code sample. Perhaps that was a typo and you didn't mean to say that. Also, You've missed the point of the OP's question (ironically, you were selected for the answer). In your solution -
ActOnConstrainedThing
will be subclassed for each pair of contraints the user wants. Using a delegate/event is no less flexible, you simple create multiple types - one for each need as well. [too much text, see next comment] -
payo about 12 yearsIn fact, you can even constrain delegate generic arguments by enclosing them in a class which defines the constraint. All anyone here is saying is that generic types are defined on use. You've not shown anything different. If you feel you have, provide a working code sample to prove it.
-
Jim about 12 yearsI've down voted this answer because I don't believe it actually answers the OP question, at least not in a clear fashion. Unsure how the OP selected this as the answer. I'd be persuaded to remove my down vote if either the OP can show how he solved his problem with this answer or the answer can be shown with a clear example of how this solves the problem.
-
supercat about 12 years@Jim: I've edited the post to give an example of how an interface which has no generic type parameters itself may include a generic method, and how such an interface might be employed in a situation where ordinary delegates would not suffice.
-
Jim about 12 years@supercat: your IShuffleFiveThings interface won't compile as is. You have to have a type parameter in the interface definition. For example interface IShuffleFiveThings<T> { void Shuffle(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5); } I don't know if you're trying to piece together an example from existing code without revealing something but what you've posted still won't work. Additionally, your ApplyShiffles<T> method would require a type to be specified OR implied by the callers parameter type. Either way the caller still has to provide the type at compile time.
-
Jim about 12 years@supercat: continued OR your interface method has to take a type parameter itself like such interface IShuffleFiveThings { void Shuffle<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5); } But the result is the same, the caller still has to provide the type, explicitly thru the typeparameter or by implicit of the method parameter type. Still the caller has to provide it at compile time.
-
supercat about 12 years@Jim: The type parameter got gobbled up by the editor. It's really annoying that sometimes the site requires less-than signs to be replaced with
<
, and sometimes requires that they not be. In any case, the code which supplies instances ofIShuffleFiveThings
does not have to know the types those instances will be used with; nor does the code forApplyShuffles<T>
. By contrast, code which supplies delegates would have to know. -
Jim about 12 years@supercat: I implemented the code as you have given and it all boils down to there still has to be a typeparameter given to the ApplyShuffles<T> which I think doesn't address the essence of the OP question. In this implementation, you can add implementations of IShuffleFiveThings to the collection of shufflers without specifying type at that point. However, all you're doing is deferring the type specification to whoever call ApplyShuffle<T>. Which may work in your example but I don't think it is a practical solution to the OP question. I think Generics is the wrong solution.
-
supercat about 12 years@Jim: I wasn't quite clear what the original poster was trying to do, but it looked as though the poster was trying to be able to subscribe to something that would behave like an event, without having to specify the parameter type until the event-like thing was raised.
-
Jim about 12 years@supercat: Now that I think the clarity of your answer is now obtainable via the updates and comments, I will remove my down vote.