C#通用事件

时间:2012-05-04 19:42:17

标签: c# events generics

我有一个带有泛型类型的委托作为参数之一:

public delegate void UpdatedPropertyDelegate<T>(
    RemoteClient callingClient, 
    ReplicableProperty<T> updatedProp, 
    ReplicableObject relevantObject
);

现在,我想要一个可以订阅的公共事件供其他类使用。因此,我做了:

public event UpdatedPropertyDelegate<T> UpdatedProperty;

然而,编译器并不喜欢这样。我不明白为什么必须在这里指定T.当然,我在发射事件时指明了它,即:

if (UpdatedProperty != null) 
{
    UpdatedProperty(this, readProperty, 
        ReplicableObjectBin.GetObjectByID(readProperty.OwnerID));
}

那么,我做错了吗?或者这是一次大规模的理解失败?

感谢。

3 个答案:

答案 0 :(得分:8)

听起来你需要的是接口类型,而不是委托。接口方法可以接受开放的泛型类型(这就是你所追求的),即使代表不能。例如,可以定义类似的内容:

interface ActOnConstrainedThing<CT1,CT2>
{
  void Act<MainType>(MainType param) where MainType: CT1,CT2;
}

即使CT1CT2的实施者不共享也实施CT1CT2的公共基本类型,Act的实现也可能会使用它的传入参数为CT1CT2而没有进行类型转换,甚至可以将其传递给期望具有CT1CT2约束的通用参数的例程。代表们无法做到这一点。

请注意,使用接口而不是委托意味着不能使用正常的“事件”机制和语法。相反,作为事件发布者的对象必须维护实现所需接口的对象实例列表(例如List<ActOnConstrainedThing<IThis,IThat>>),并枚举该列表上的实例(可能使用foreeach)。例如:

List<IActOnConstrainedThing<IThis,IThat>> _ActOnThingSubscribers;

void ActOnThings<T>(T param) where T:IThis,IThat
{
  foreach(var thing in _ActOnThingSubscribers)
  {
    thing.Act<T>(param);
  }
}

修改/附录

我使用这种模式的地方还有其他一些看起来与这个问题不太相关的东西,根据我的解释,我的解释是如何让一个代表(或等价物)具有开放式参数,以便调用委托等价物的对象可以提供类型参数,而没有提供委托的对象必须事先知道它。大多数情况下,这是有用的涉及泛型约束,但由于这显然引入了混淆,这里的例子不是:

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

IShuffleFiveThings.Shuffle<T>方法通过ref获取五个参数并对它们执行某些操作(最有可能以某种方式置换它们;可能随机地置换它们,或者可能随机地置换一些,同时将其他位置留在原处。如果有一个列表IShuffleFiveThings,那么该列表中的内容可以有效地使用,无需装箱或反射,来操纵任何类型的东西(包括类类型和值类型)。相比之下,如果有人使用代表:

delegate void ActOn5RefParameters(ref p1, ref p2, ref p3, ref p4, ref p5);

然后因为任何特定的委托实例只能对创建时提供的单个参数类型起作用(除非它是一个只通过Reflection调用的开放委托),否则需要创建一个单独的委托列表对于每种类型的对象,一个人希望改组(是的,我知道通常会通过使用整数索引数组来处理排列;我选择置换作为一个操作,因为它适用于所有对象类型,而不是因为这种特定的置换方法事情是有用的。)

请注意,因为T中的IShuffleFiveThings类型没有任何约束,所以除了通过类型转换(可能会引入装箱)之外,实现将无法使用它。为这些参数添加约束使它们更有用。虽然可以在接口内对这些约束进行硬编码,但这会限制接口对需要这些特定约束的应用程序的有用性。使约束本身通用可以避免这种限制。

答案 1 :(得分:4)

您实际上是在创建该委托的实例。实例需要定义其泛型类型。

您的委托的定义可以包含T,但您的实例需要定义哪个T.

答案 2 :(得分:3)

给出这个例子:

public delegate void FooDelegate<T>(T value);

public class FooContainer
{
    public event FooDelegate<T> FooEvent;
}

示例中的编译器不喜欢FooEvent声明,因为未定义T。但是,将FooContainer更改为

public delegate void FooDelegate<T>(T value);

public class FooContainer<T>
{
    public event FooDelegate<T> FooEvent;
}

现在编译器还可以,因为创建FooContainer实例的人现在必须像这样指定类型T

FooContainer<string> fooContainer = new FooFooContainer<string>();

但是,您也可以将T约束到这样的界面。

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
}

在这种情况下,您可以使用类型引发事件,只要它们实现接口IFooValue