C# Passing generic class as an event parameter

喜你入骨 提交于 2021-02-05 11:03:20

问题


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?


回答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.




回答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
    }
}


来源:https://stackoverflow.com/questions/27904362/c-sharp-passing-generic-class-as-an-event-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!