C# 传递泛型 class 作为事件参数

C# Passing generic class as an event parameter

过去几天我一直在使用泛型,我在 events/delegates 中尝试将泛型类型作为参数传递时遇到了问题。

stack 的一位非常友好的成员可以为我上周五发布的一个关于泛型的问题提供一些线索。但是,我想再次提出这个问题,因为我仍然不明白我应该如何让它正常工作。

在下面的代码中你可以看到我有一个 class "Catalog",它可以引发一个事件,其中包含一个通用参数。

class "MainWindow" 订阅了这个事件并且应该使用通用参数。

这不起作用,因为事件必须指定委托的泛型类型。

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
   }

}

有没有办法在class"MainWindow"中接收泛型参数? 还是我需要实施一些变通办法?

您可以使目录 class 通用。这将允许您使用 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
    }
}

根据您的具体需要,您可能能够保留方法的通用方面。但我承认,我不清楚 为什么 你会想要那样做。该方法绑定到特定的委托类型,即该方法具体引发的事件使用的类型。

让它完全通用有什么价值?你真的要用各种不同的列表引发这个事件吗?每个列表都有不同的元素类型?如果是这样,事件处理程序应该如何知道为哪个列表引发事件?我的意思是,它可以通过反射来完成,但这并不是一个好的设计。

无论如何,如果你真的想让你的代码编译,你需要对涉及的 class 施加一些限制,并使用接口而不是实际的 class在委托类型声明中。

例如:

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));
    }
}

这声明了接口 IBaseItemEventArgs<out T>,即 T 是协变的(即返回的实际类型可能比使用该接口的声明中使用的类型更派生),以及事件委托类型 GenericListItemCountChangedEvent<in T>,即 T 是逆变的(即分配给方法处理程序的委托类型可能具有比局部处理程序变量声明中使用的参数派生更少的参数)。

这两个一起允许:

  1. RaiseGenericListItemCountChangedEvent<T>() 方法从事件委托类型隐式转换为处理程序类型,该类型的声明类型比方法声明中的 T 更少派生处理。
  2. IBaseItemEventArgs<out T>接口有一个方法,returns一个类似协变的接口对象,IReadOnlyList<T>

当然,如果处理程序需要能够修改列表,那么代码将无法编译(不能使用IReadOnlyList<T>)。但是这是一件好事。如果调用者能够使用比声明的更派生的列表元素类型订阅事件 and 仍然允许更改列表,它可能会将错误类型的元素添加到列表.那一点都不好。 :) 所以语言规则可以防止你犯那个错误。