开放实例委托上的协变实例类型

问题描述:

我试图为共享共同签名的方法创建开放实例委托,但在许多不同和不相关的类型上定义。这些方法使用自定义属性进行标记,并且在运行时我查找所有使用此属性标记的方法,以便从其MethodInfo s中构建代表。例如,给定的委托:开放实例委托上的协变实例类型

delegate void OpenActionDelegate(object instance, float someParam); 

我想匹配的方法:

void Foo.SomeAction(float someParam); 
void Bar.SomeOtherAction(float someParam); 

FooBar是完全不相关的类。与MethodInfo的任一方法武装,我希望最终能够得到一个开放的委托,像这样:

MethodInfo fm = typeof(Foo).GetMethod("SomeAction", BindingFlags.Public | BindingFlags.Instance); 
MethodInfo bm = typeof(Bar).GetMethod("SomeOtherAction", BindingFlags.Public | BindingFlags.Instance); 
OpenActionDelegate fd = (OpenActionDelegate)Delegate.CreateDelegate(typeof(OpenActionDelegate), fm); 
OpenActionDelegate bd = (OpenActionDelegate)Delegate.CreateDelegate(typeof(OpenActionDelegate), bm); 

我遇到的问题是在委托明确实例规范的类型。由于这些方法没有保证的基本类型,所以我们只是设置了object。但试图绑定MethodInfo失败,可能是因为绑定代表时参数类型不是协变的。切换代理签名以使实例参数类型为FooBar适用于绑定相应的MethodInfo

我不相信实际上可能像这样绑定一个开放委托,因为那么显式实例参数将不会是调用该方法的适当类型。我困扰的是它可能将已关闭的委托绑定到任何声明类型的MethodInfo,因为它不包含麻烦的实例类型。例如,我可以将已关闭的代表绑定到null实例,然后在调用它们之前在代表上使用GetField("_target").SetValue(del, instance)。但是这有点ha。。

现在,万一有其他解决办法,我要做到这一点的原因是为了避免堆分配和价值型拳直接调用MethodInfo■当,即:

someActionInfo.Invoke(instance, new object[] { someParam }); 

这使拳击的float类型,并且object[]数组在堆上分配,这两个数组都慢慢地产生堆垃圾,用于另外的一次性调用。

参数类型,包括隐含的“this”参数,显然不能是协变,仍然是类型安全的。暂时忘记实例方法,并考虑静态方法。如果你有

static void Foo(Mammal m) {} 

,那么你不能分配给一个委托,它需要一个动物,因为那代表的主叫方可能会在水母通过。你可以把它分配给一个代表长颈鹿的委托,因为那时候调用者只能通过长颈鹿,而长颈鹿则是哺乳动物。

总之,是类型安全,你需要逆变,在参数不协方差

C#通过几种方法支持。首先,在C#4你可以这样做:

Action<Mammal> aa = m=>m.GrowHair(); 
Action<Giraffe> ag = aa; 

也就是说,在一般的动作类型转换逆变当不同类型的参数都是引用类型

第二,在C#2和上面可以做到这一点:

Action<Giraffe> aa = myMammal.GrowHair; 

即,方法组转换委派是在参数类型的方法的逆变。

但是你想要的协变类型不是类型安全的,因此不支持。

+0

当严格考虑手册“this”作为参数时,这是有道理的。但是,当绑定开放委托时,我实际上有一个具有有效声明类型的方法信息 - 因此,我希望CLR能够让它绑定它,并且如果我后来用无效类型调用它,冒着自我伤害的风险。但我想这不是这种情况! – Camille 2011-05-18 04:12:37

你的问题是,你想创建一个委托,做两件事 - 一个强制转换和一个方法调用。如果有可能,你会使用泛型做到这一点:

public OpenActionDelegate GetDelegate<T>(MethodInfo method) { 
    return (object instance, float someParam) => { 
     ((T)instance).method(someParam); 
    }; 
} 

不幸的是,第一次只能用做仿制药,只有用反射第二 - 所以你不能将二者结合起来!

但是,如果您创建一次委托并多次使用它们(看起来是这样),那么动态编译执行它的表达式可能会很有效。 Expression<T>的美妙之处在于你可以用它做什么 - 你基本上是元编程。

public static OpenActionDelegate GetOpenActionDelegate(Type type, string methodName) { 
    MethodInfo method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance); 

    ParameterExpression instance = Expression.Parameter(typeof(object)); 
    ParameterExpression someParam = Expression.Parameter(typeof(float)); 

    Expression<OpenActionDelegate> expression = Expression.Lambda<OpenActionDelegate>(
     Expression.Call(
      Expression.Convert(
       instance, 
       type 
      ), 
      method, 
      someParam 
     ), 
     instance, 
     someParam 
    ); 

    return expression.Compile(); 
} 

这个方法将编译并返回一个OpenActionDelegate抛给它的参数type和调用methodName。下面是一些例子用法:

public static void Main() { 
    var someAction = GetOpenActionDelegate(typeof(Foo), "SomeAction"); 
    var someOtherAction = GetOpenActionDelegate(typeof(Bar), "SomeOtherAction"); 

    Foo foo = new Foo(); 
    someAction(foo, 1); 

    Bar bar = new Bar(); 
    someOtherAction(bar, 2); 

    // This will fail with an InvalidCastException 
    someOtherAction(foo, 2); 

    Console.ReadKey(true); 
} 
+0

这实际上是一个非常优雅的选择!我不知道表达式。不幸的是,命名空间不适用于框架的Xbox 360版本,所以我实际上无法使用它。但很高兴知道它在那里。 – Camille 2011-05-20 15:06:17

所以事实证明,在Xbox 360 .NET框架是不是很喜欢使用反射来改变非公有制领域。 (阅读:它彻底拒绝。)我想这是为了防止反向工程一些敏感的XDK信息。无论如何,这排除了我最终使用的这个问题的小反省。

但是,我做了一些东西与一般代表。替换:

delegate void OpenActionDelegate(object instance, float someParam); 

有了:

delegate void OpenActionDelegate<T>(T instance, float someParam); 

在最初的反思,我有MethodInfo所有相关类型,这意味着我可以使用反射和MakeGenericType创建无动作类型安全的代表必须手工创建它们。由此产生的公开委托完美地绑定,所以我的委托列表被填充。但是,这些存储为普通的Delegate s,这意味着我无法安全地访问它们而不使用禁止性的DynamicInvoke

事实证明,动作调用方法最初是通过它的实例作为object S,事实证明,我可以,事实上,使它通用,以获得正确的类型为通用的代表,并投在Delegate背上一个OpenActionDelegate<Foo>,例如:

internal static void CallAction<T>(int actionID, T instance, float param) 
{ 
    OpenActionDelegate<T> d = (OpenActionDelegate<T>)_delegateMap[actionID]; 

} 

于是就这样,我干脆躲闪的协方差问题,得到一个直接委托调用的速度和避免装箱。唯一不方便的是这种方法不适用于继承,因为委托绑定到它们的声明类型。如果我需要支持继承,我将不得不大大改进反射过程。

由于委托人早于泛型,他们不能完成通用接口所能做的所有事情。我不太清楚你想要做什么,但是如果你可以添加合适的接口到你想要调用的方法的类,它看起来好像是接口可以不需要Reflection的东西。例如,如果你有兴趣的方法在IWoozable并采取float参数,那么你可以定义一个接口IWoozer与方法void Woozle(IWoozable target, float someParam);一个IWoozer实现可以是

 
void Woozle(IWoozable target, float someParam) 
{ 
    target.Method1(someParam); 
} 

另一种可能是类似的,但使用方法2等等。人们可以轻松地放入任意代码而不需要委托,并且可以选择独立于目标选择的动作。

另一件可以用通用接口做的事情不能由委托人完成,这包括开放的通用方法。例如,一个可具有一个接口

 
interface IActUpon<T> 
{ 
    void Act(ref T target); 
    void ActWithParam<PT1>(ref T target, ref PT param); 
} 

保持那么T可将其暴露在象上面的方法的类:

 
    void ActUponMyThing<ActorType>(ref ActorType Actor) where ActorType:IActUpon<T> 
    { 
    Actor.Act(ref T myThing); 
    } 
    void ActUponMyThingWithParam<ActorType,PT>(ref IActUpon<PT> Actor, ref PT param) 
    where ActorType:IActUpon<T> 
    { 
    Actor.ActWithParam(ref T myThing, ref PT param); 
    } 

各接口的使用约束的一般类型的使得它在某些情况下可能使用结构并避免拳击,这是代表们无法做到的。此外,即使实现约束的类不共享公共基类型,开放泛型方法也可能将多个约束应用于其泛型类型参数,并调用同样具有多个约束的泛型方法。