简体   繁体   English

C#通用事件

[英]C# Generic Events

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. 我不明白为什么必须在这里指定T. Surely it's specified when I fire the event, ie: 当然,它是在我发射事件时指定的,即:

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. 谢谢。

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. 即使CT1CT2实现者不共享也实现CT1CT2的公共基类型, Act的实现可以使用其传入的参数作为CT1CT2而不进行类型转换,甚至可以将其传递给期望a的例程具有CT1CT2约束的通用参数。 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 (eg a List<ActOnConstrainedThing<IThis,IThat>> ) , and enumerate the instances on that list (perhaps using foreeach ). 相反,作为事件发布者的对象必须维护一个实现所需接口的对象实例列表(例如List<ActOnConstrainedThing<IThis,IThat>> ),并枚举该列表上的实例(可能使用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. 我使用这种模式的地方还有一些其他东西似乎与这个问题不太相关,我的解释是询问如何使用open类型参数获得委托(或等效),以便调用对象委托等价物可以提供类型参数,而没有提供委托的对象必须事先知道它。 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: IShuffleFiveThings.Shuffle<T>方法通过ref接受五个参数并对它们做一些事情(很可能以某种方式对它们进行置换;可能随机地置换它们,或者可能随机地置换一些,同时将其他地方IShuffleFiveThings其他位置。如果有一个列表IShuffleFiveThings ,该列表中的东西可以有效地使用,无需装箱或反射,来操纵任何类型的东西(包括类类型和值类型)。相比之下,如果要使用委托:

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

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). 请注意,因为IShuffleFiveThings中的类型T没有任何约束,所以除了通过类型转换(可能引入装箱)之外,实现将无法对它做很多事情。 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. 使约束本身通用避免了这种限制。

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. 您的委托的定义可以包含T,但您的实例需要定义哪个T.

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. 您的示例中的编译器不喜欢FooEvent声明,因为未定义T However, changing FooContainer to 但是,将FooContainer更改为

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实例的人现在必须像这样指定类型T.

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

However you could also constrain T to an interface like this. 但是你也可以将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
}

In this case, you can raise the event with types as long as they implement the interface IFooValue . 在这种情况下,只要实现了IFooValue接口,就可以使用类型引发事件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM