0

I've been working with generics over the last few days and I encountered an issue by trying to pass generic types as parameters in events/delegates.

A very friendly member of stack could bring some light into one question about generics I posted last Friday. However, I'd like to bring this up again, because I still don't understand how I am supposed to make it work correctly.

In the following code you can see that I have a class "Catalog", which can raise an event, which contains a generic parameter.

The class "MainWindow" subscribes to this event and should work with the generic parameter.

This doesn't work, because the event must specify the generic type of the delegate.

public class Catalog {

#region EVENTS

public delegate void GenericListItemCountChangedEvent<T>(object sender, GenericListItemCountChangedEventArgs<T> e) where T : BaseItem; 
//This is the point where it does not work, because I specify BaseItem as the type
public event GenericListItemCountChangedEvent<BaseItem> GenericListItemCountChanged;

private void RaiseGenericListItemCountChangedEvent<T>(List<T> List) where T : BaseItem {
    if (GenericListItemCountChanged != null) {
        GenericListItemCountChanged(this, new GenericListItemCountChangedEventArgs<T>(List));
    }
}

public class GenericListItemCountChangedEventArgs<T> : EventArgs where T : BaseItem {
    private List<T> _changedList_propStore;
    public List<T> ChangedList {
        get {
            return _changedList_propStore;
        }
    }

    public GenericListItemCountChangedEventArgs(List<T> ChangedList){
        _changedList_propStore = ChangedList;
    }
}

#endregion EVENTS

}


public partial class MainWindow : Window{

   public MainWindow(){
      _catalog.GenericListItemCountChanged += (sender, e) => GenericListItemCountChanged(sender, e);
   }

   private void GenericListItemCountChanged<T>(object sender, Catalog.GenericListItemCountChangedEventArgs<T> e) where T : BaseItem {
       //Use Generic EventArgs
   }

}

Is there a way to receive the generic parameter in the class "MainWindow"? Or do I need to implement some work-around?

Tsahi Asher
  • 1,767
  • 15
  • 28
Noel Widmer
  • 4,444
  • 9
  • 45
  • 69
  • if you're specifying `T` as being a `BaseItem`, why not just use `BaseItem` instead? – DLeh Jan 12 '15 at 14:36
  • 1
    @DLeh I specify T as being a subtype of BaseItem (T : BaseItem). But I cannot specify T in the event, that is why I had to hardcode "BaseItem" in the event. But that pretty much is the point where stuff does not work. – Noel Widmer Jan 12 '15 at 14:37
  • @NoelWidmer You might want to try looking at [this question](http://stackoverflow.com/questions/9956596/eventhandlers-and-covariance). It'll likely clarify some things for you. – Kyle Jan 12 '15 at 15:10

2 Answers2

2

ou can make Catalog class generic. That will allow you to use GenericListItemCountChangedEvent T

public class Catalog<T> where T: BaseItem
{
    public delegate void GenericListItemCountChangedEvent<T>(object sender, GenericListItemCountChangedEventArgs<T> e) where T : BaseItem;
    //This is the point where it does not work, because I specify BaseItem as the type
    public event EventHandler<GenericListItemCountChangedEventArgs<T>> GenericListItemCountChanged;

    private void RaiseGenericListItemCountChangedEvent(List<T> List)
    {
        if (GenericListItemCountChanged != null)
        {
            GenericListItemCountChanged(this, new GenericListItemCountChangedEventArgs<T>(List));
        }
    }

    public class GenericListItemCountChangedEventArgs<T> : EventArgs where T : BaseItem
    {
        private List<T> _changedList_propStore;
        public List<T> ChangedList
        {
            get
            {
                return _changedList_propStore;
            }
        }

        public GenericListItemCountChangedEventArgs(List<T> ChangedList)
        {
            _changedList_propStore = ChangedList;
        }
    }
}

public  class MainWindow 
{
    public MainWindow()
    {
        new Catalog<BaseItem>().GenericListItemCountChanged += (sender, e) => GenericListItemCountChanged(sender, e);
    }

    private void GenericListItemCountChanged<T>(object sender, Catalog<BaseItem>.GenericListItemCountChangedEventArgs<T> e) where T : BaseItem
    {
        //Use Generic EventArgs
    }
}
smiech
  • 725
  • 4
  • 8
  • 1
    This is true, but that means, that the event can only handle the T of the class. My instance of the class "Catalog" must handle multiple 'T's. That means, the event cannot rely on the 'T' specified in the class "header". – Noel Widmer Jan 12 '15 at 15:01
  • 1
    if all your 'T' s inherits from baseitem you will be able to handle what you expect – BRAHIM Kamel Jan 12 '15 at 15:03
  • @KamelBRAHIM Ok, let me try... If I want to adress an enum in the Catalog, then I have to use Catalog.Value1. Right? Is Catalog.Value1 the same? – Noel Widmer Jan 12 '15 at 15:08
  • an enum is a constant and cannot considered as a reference type you have to use object if you want to pass various type of items ?? – BRAHIM Kamel Jan 12 '15 at 15:23
  • @KamelBRAHIM The compiler does not allow me to use Catalog.MyEnum.MyValue, using _catalog.MyEnum.MyValue does not work of corse. The only way it accepts it is to specify the 'T' of Catalog like this: Catalog.MyEnum.MyVal – Noel Widmer Jan 12 '15 at 15:50
  • 1
    @NoelWidmer: that's correct. That's because this answer doesn't really do what you want; it requires that you have a completely different `Catalog` type for each `BaseItem` subclass you want to use. That means a different `Catalog` instance for each subclass type, and different nested types for each subclass type. This does not seem like an appropriate approach to me. You can fix the latter issue by splitting the class into a base `Catalog` type where the nested types exist and the `Catalog` type where the event can exist. But then you still have different instances per subclass type. – Peter Duniho Jan 13 '15 at 06:38
  • @PeterDuniho Thanks for clearing it up for me! As far as I understand the compiler ("or not the compiler?") creates a new "class" for each type of 'T' and thus the Class "Catalog" itself does not exists anymore. – Noel Widmer Jan 13 '15 at 07:06
  • 1
    @NoelWidmer: yes, that's correct. There is no single `Catalog` as a concrete type; members of the type are usable only when the type parameter is actually provided, and doing so effectively causes a whole new type for that parameter to be created. – Peter Duniho Jan 13 '15 at 07:18
1

Depending on your exact needs, you might be able to preserve the generic aspect of your method. But I admit, it's not clear to me why you would want to do that. The method is tied to a specific delegate type, i.e. the type used by the event that method specifically raises.

Of what value would there be in having it generic at all? Are you really going to raise this one event with various different lists, each having different element types? If so, how is the event handler supposed to know what list the event is being raised for? I mean, it can be done via reflection, but that's hardly good design.

In any case, if you really want to get your code to compile, you will need to impose some restrictions on the classes involved, and use an interface instead of an actual class in the delegate type declaration.

For example:

interface IBaseItemEventArgs<out T>
{
    IReadOnlyList<T> ChangedList { get; }
}

class GenericListItemCountChangedEventArgs<T> : EventArgs, IBaseItemEventArgs<T>
    where T : BaseItem
{
    private IReadOnlyList<T> _changedList_propStore;
    public IReadOnlyList<T> ChangedList
    {
        get
        {
            return _changedList_propStore;
        }
    }

    public GenericListItemCountChangedEventArgs(List<T> ChangedList)
    {
        _changedList_propStore = ChangedList.AsReadOnly();
    }
}

public delegate void
    GenericListItemCountChangedEvent<in T>(object sender, IBaseItemEventArgs<T> e)
    where T : BaseItem;

public static event
    GenericListItemCountChangedEvent<BaseItem> GenericListItemCountChanged;

private static void RaiseGenericListItemCountChangedEvent<T>(List<T> List)
    where T : BaseItem
{
    GenericListItemCountChangedEvent<T> handler = GenericListItemCountChanged;

    if (handler != null)
    {
        handler(null, new GenericListItemCountChangedEventArgs<T>(List));
    }
}

This declares the interface IBaseItemEventArgs<out T>, i.e. with T being covariant (i.e. the actual type returned may be more derived than that used in the declaration that uses the interface), and the event delegate type GenericListItemCountChangedEvent<in T>, i.e. with T being contravariant (i.e. the type of the delegate assigned to the method's handler may have arguments less derived than that used in the declaration of that local handler variable).

These two together allow:

  1. The RaiseGenericListItemCountChangedEvent<T>() method to implicitly convert from the event delegate type, which has a declared type that is less-derived than the T in the method declaration, to the handler type the method actually deals with.
  2. The IBaseItemEventArgs<out T> interface to have a method which returns a similarly covariant interface object, IReadOnlyList<T>.

Of course, if the handler needs to be able to modify the list, then the code won't compile (can't use IReadOnlyList<T>). But that's a good thing. If a caller was able to subscribe to the event with a list element type more derived than that declared and still be permitted to change the list, it could add elements of the wrong type to the list. That wouldn't be good at all. :) So the language rules prevent you from making that mistake.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • I will try this today. Thx for the lead. Yes I have multiple lists of different types wich are ItemSources of several Listviews. As a simple example: I have a method to get the Listview from the type of list, wich allows me to get the correct Listview from the generic list and refersh it. This requires a single call to listview.refersh rather than multiple refersh calls for each type of listview (= type of list). In case I will add more types of lists in the future this will prevent having to extend multiple methods, wich do the same thing for different types of lists. I'll be back soon... – Noel Widmer Jan 13 '15 at 06:38
  • Alright, I implemented your code and it compiles just fine. I understand that 'T' in 'IBaseItemEventArgs' can be more derived than the 'T' in the delegat. But when I try to get the "real" type of 'T' in the method, wich subscribes to the event it returns 'RuntimeType'. So basically the Type of the List is 'BaseItem' and the generic Type of the list is 'RuntimeType'. However, I might be over-engeneering this, or it is even bad Design as you mentioned. I think I also kinda lost track of what I am really trying to do by implementing Generics whenever I could. – Noel Widmer Jan 13 '15 at 07:51
  • 1
    @NoelWidmer: "when I try to get the "real" type of 'T' in the method" -- how do you do that? If you try to use reflection to get the type from a `MethodInfo` object, I'd expect you to see `RuntimeType`. But in the method itself, you should be able to use the expression `typeof(T)` and get the correct type. I do think that if you don't remember why you're trying to do this, you might reconsider the use of generics here, at least until you can recall the motivation. :) – Peter Duniho Jan 13 '15 at 16:40
  • I used something like typeof(T).GetGenericAtributes. And no, T was actually a BaseItem since the event speciefies T to be of type BaseItem I guess. (Could be because I use Lists...?) Thank you very much for all the help and information, it is very usefull. I use Generics everywhere in my project and I thought it would be nice to have it in the event as well. Just for consistency. However, I did now solve the issue with a non Generic solution. Thank you very much! – Noel Widmer Jan 14 '15 at 07:18