Delegate.CreateDelegate不会包含返回值 - 故意还是省略?

And*_*tan 8 .net c#

我有一个静态方法:

public class Example
{
    //for demonstration purposes - just returns default(T)
    public static T Foo<T>() { return default(T); }
}
Run Code Online (Sandbox Code Playgroud)

我需要能够使用Type可能很多的参数调用来调用它,所以我的标准模式是创建一个线程安全的委托缓存(ConcurrentDictionary在.Net 4中使用),它Foo<T>使用正确的方法动态调用该方法T.但是,如果没有缓存,代码是这样的:

static object LateFoo(Type t) 
{ 
  //creates the delegate and invokes it in one go
  return (Func<object>)Delegate.CreateDelegate( 
    typeof(Func<object>), 
    typeof(Example).GetMethod("Foo", BindingFlags.Public | BindingFlags.Static). 
      MakeGenericMethod(t))(); 
}
Run Code Online (Sandbox Code Playgroud)

这不是我第一次这样做 - 而且在过去我使用Expression树来构建和编译代理来调用目标方法 - 确保返回类型转换和装箱从int - > object(for示例)处理正确.

更新 - 有效的表达式代码示例

static object LateFoo(Type t)
{
  var method = typeof(Example)
               .GetMethod("Foo", BindingFlags.Public | BindingFlags.Static)
               .MakeGenericMethod(t); 
  //in practise I cache the delegate, invoking it freshly built or from the cache
  return Expression.Lambda<Func<IField, object>>(Expression.Convert(
    Expression.Call(method), typeof(object))).Compile()();
}
Run Code Online (Sandbox Code Playgroud)

有点有趣的是,我很早就学会了明确Convert需要并接受它的表达式- 而在这里代替答案它现在有意义为什么.Net框架不会自动粘贴等效物.

结束更新

但是,这次我认为我只是使用,Delegate.CreateDelegate因为它很好地发挥了(来自MSDN)的事实:

类似地,如果方法的返回类型比委托的返回类型更具限制性,则委托的返回类型与方法的返回类型兼容,因为这可以保证方法的返回值可以安全地转换为委托的返回类型.

现在 - 如果我typeof(string)转到LateFoo方法,一切都很好.

但是,如果我通过typeof(int)我得到ArgumentExceptionCreateDelegate电话,邮件:Error binding to target method.没有内部例外或进一步的信息.

因此,对于方法绑定而言,似乎object不会被认为比限制更严格int.显然,这必须与拳击是一个不同的操作,而不是简单的类型转换和值类型object在.Net框架中不被视为协变; 尽管在运行时有实际的类型关系.

C#编译器似乎同意这一点(只是最简单的方法我可以模拟错误,忽略代码会做什么):

public static int Foo()
{
    Func<object> f = new Func<object>(Foo);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

不编译,因为Foo方法'有错误的返回类型' - 考虑到CreateDelegate问题,C#只是跟随.Net的领先.

在我看来,.Net在协方差的处理方面是不一致的 - 要么价值类型是,object要么不是; 如果它不是它不应该object作为一个基础暴露(尽管它将使我们的生活更加困难).因为它确实object是作为基础公开的(或者它只是那种语言?),那么根据逻辑,值类型应该是协变的object(或者你应该说出来的任何方式)使这个委托绑定正确.如果该协方差只能通过拳击手术来实现; 然后框架应该照顾到这一点.

我敢说这里的答案是CreateDelegate并没有说它会在协方差中处理一个盒子操作,因为它只使用了'cast'这个词.我还期望在价值类型和对象协方差这一更广泛的主题上有完整的论文,而且我在大肆宣扬一个长期存在且已经解决的问题.我认为有些东西我不明白或者错过了 - 所以请指教!

如果这是无法回答的 - 我很乐意删除.

Cod*_*aos 7

如果可以使用表示保存转换转换参数和返回值,则只能以这种方式转换委托.

  • 引用类型只能以这种方式转换为其他引用类型
  • 积分值可以转换为相同大小的其他整数值(int,uint和相同大小的枚举兼容)

一些更相关的博客文章:

这种二分法推动了另一种转换分类方案(†).我们可以将转换划分为表示保留转换(B到D)和表示更改转换(T到U).(‡)我们可以将引用类型的表示保留转换视为保留对象标识的转换.当你将B转换为D时,你没有对现有对象做任何事情; 你只是在验证它实际上是你所说的类型,然后继续前进.对象的标识和表示引用的位保持不变.但是当你将int转换为double时,结果位是非常不同的.

这就是为什么接口和委托类型的协变和逆变转换要求所有变化类型参数都是引用类型的原因.为确保变体引用转换始终保持身份,所有涉及类型参数的转换也必须是保持身份的.确保类型参数上的所有非平凡转换都是保持身份的最简单方法是将它们限制为引用转换. http://blogs.msdn.com/b/ericlippert/archive/2009/03/19/representation-and-identity.aspx

"但是一个值类型,比如int,它是32位内存,不多也不少,可能从对象继承?一个在内存中布局的对象大于32位;它有一个同步块和一个虚拟功能表和那里的各种东西." 显然很多人认为继承与价值在记忆中的布局有关.但是如何在内存中布置一个值是一个实现细节,而不是继承关系的契约义务!当我们说int继承自object时,我们的意思是如果object有一个成员 - 比如ToString - 那么int也有该成员. http://ericlippert.com/2011/09/19/inheritance-and-representation/


Eri*_*ert 6

在我看来,.Net在协方差的处理方面是不一致的 - 价值类型是一个对象,或者它不是; 如果它不是它不应该暴露对象作为基础

正如克林顿总统所说的那样,这取决于"是"的含义.

出于协方差的目的,int不是对象,因为int不是与对象兼容的赋值.类型对象的变量期望具有特定含义的特定位模式存储在其中.int类型的变量期望具有特定含义的特定位模式,但具有对象类型的变量的含义不同的含义.

但是,出于继承的目的,int是一个对象,因为object的每个成员也是int的成员.如果你想调用一个对象 - ToString,比如说 - on int,你可以保证你可以这样做,因为int是一种对象,而一个对象有ToString.

不幸的是,我同意,"一个int是一个对象"的真值取决于你的意思是"与赋值兼容"还是"是一种".

如果该协方差只能通过拳击手术来实现; 然后框架应该照顾到这一点.

好.哪里?拳击行动应该去哪里?某人,某处必须产生一大块具有拳击指令的IL.您是否建议在框架看到时:

Func<int> f1 = ()=>1;
Func<object> f2 = f1;
Run Code Online (Sandbox Code Playgroud)

然后框架应该自动假装你说:

Func<object> f2 = ()=>(object)f1();
Run Code Online (Sandbox Code Playgroud)

从而产生拳击指令?

这是一个合理的功能,但后果是什么?Func<int>并且Func<object>是参考类型.如果你这样做f2 = f1的引用类型,你不期望f2和f1具有引用标识吗?这个测试用例失败不是很奇怪吗?

f2 = f1;
Debug.Assert(object.ReferenceEquals(f1, f2));
Run Code Online (Sandbox Code Playgroud)

因为如果框架实现了该功能,它会.

同样,如果你说:

f1 = MyMethod;
f2 = f1;
Run Code Online (Sandbox Code Playgroud)

你问两位代表他们是否提到了相同的方法,如果他们提到不同的方法,是不是非常奇怪?

我觉得这很奇怪.但是,VB设计师却没有.如果你试图在VB中提取类似的恶作剧,编译器将不会阻止你. VB代码生成器将为您生成引用不同方法的非引用相等的委托.试试吧!

故事的道德:也许C#不是你的语言.也许您更喜欢像VB这样的语言,其语言旨在"猜测用户可能意味着什么,并使其成功"的态度.这不是C#设计师的态度.我们更"告诉用户什么时候看起来很可疑,并让他们弄清楚他们想要解决的问题".


Igo*_*hov 5

尽管我认为@CodeInChaos 是绝对正确的,但我还是忍不住指出这篇 Eric Lippert 的博文。在回复他的帖子的最后一条评论时(在页面的最底部),埃里克解释了这种行为的基本原理,我认为这正是您感兴趣的。

更新:正如@Sheepy 指出的那样,微软将旧的 MSDN 博客移入存档并删除了所有评论。幸运的是,Wayback Machine 保留了原始形式的博客文章