C# Generic Events

13,094

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.

Share:
13,094
Alex
Author by

Alex

Updated on June 09, 2022

Comments

  • Alex
    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
    payo about 12 years
    Or be in a generic class that define T
  • Alex
    Alex about 12 years
    So, 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
    Jean-Bernard Pellerin about 12 years
    True, in that case T is defined by the class instance, so the delegate instance is also defined, which holds with what I stated.
  • Jim
    Jim about 12 years
    This 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
    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
    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
    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
    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
    Jim about 12 years
    @payo - that where I am getting hung up. Failing to see how this solves the type parameter issue.
  • supercat
    supercat about 12 years
    @payo: An instance of ActOnThing<Foo,Bar> can be called with any generic parameter which complies with constraints Foo and Bar, and its implementation can use the passed-in parameter as a Foo or Bar without typecasting (if one didn't mind typecasting the parameter to use it as a Foo or Bar, one could simply accept a parameter of type Object and not bother with generics). One does not have to know, when generating an instance of a type implementing ActOnThing<Foo,Bar>, what the type of the parameter will be.
  • payo
    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
    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 both Foo and Bar, but the routine which calls ActOnThing<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 of ActOnThing<IThis,IThat> stored in its list.
  • payo
    payo about 12 years
    You 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
    payo about 12 years
    In 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
    Jim about 12 years
    I'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
    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
    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
    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
    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 &lt;, and sometimes requires that they not be. In any case, the code which supplies instances of IShuffleFiveThings does not have to know the types those instances will be used with; nor does the code for ApplyShuffles<T>. By contrast, code which supplies delegates would have to know.
  • Jim
    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
    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
    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.