8

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

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

那么,我做错了什么简单的事情吗?或者这是一个巨大的理解失败?

谢谢。

4

3 回答 3

8

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

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

即使CT1andCT2的实现者不共享同样实现CT1and的公共基类型CT2,其实现Act也可以将其传入的参数用作CT1CT2不使用类型转换,甚至可以将其传递给期望带有CT1CT2约束的泛型参数的例程。对于代表来说,这样的事情是不可能的。

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

List<IActOnConstrainedThing<IThis,IThat>> _ActOnThingSubscribers;

void ActOnThings<T>(T param) where T:IThis,IThat
{
  foreach(_ActOnThingSubscribers 中的变量)
  {
    thing.Act<T>(参数);
  }
}

编辑/附录

我使用这种模式的地方也有一些其他的东西似乎与问题不太相关,根据我的解释,这是询问如何拥有一个带有开放类型参数的委托(或等价物),以便对象调用委托等效项可以提供类型参数,而提供委托的对象不必事先知道它。大多数有用的情况都涉及通用约束,但由于这显然会引入混淆,所以这里有一个例子:

接口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(_ShuffleSubscribers 中的 var shuffler)
  {
    thing.Shuffle(参考 p1,参考 p2,参考 p3,参考 p4,参考 p5);
  }
}

IShuffleFiveThings.Shuffle<T>方法接受五个参数ref并对其进行处理(很可能以某种方式排列它们;也许随机排列它们,或者可能随机排列一些而将其他的留在原处。如果有一个列表IShuffleFiveThings,则该列表中的事物可以是有效地使用,无需装箱或反射,来操作任何类型的东西(包括类类型和值类型)。相比之下,如果要使用委托:

委托无效ActOn5RefParameters(参考p1,参考p2,参考p3,参考p4,参考p5);

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

请注意,由于类型TinIShuffleFiveThings没有任何约束,因此除了类型转换(可能会引入装箱)外,实现将无法对它做很多事情。向这些参数添加约束使它们更加有用。虽然可以在接口内硬编码此类约束,但这会限制接口对需要这些特定约束的应用程序的有用性。使约束本身具有通用性可以避免该限制。

于 2012-05-04T19:56:03.237 回答
4

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

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

于 2012-05-04T19:46:14.460 回答
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

于 2012-05-04T20:19:23.670 回答