使用动态参数的方法重载决策

8

这个问题可能之前已经有人回答过了。我看到很多关于“动态方法重载解析”的问题,但没有一个是特别涉及传递dynamic参数的。在下面的代码中,在Test方法中,最后一次对M方法的调用无法被解析(它无法编译)。错误提示为:该调用在[M的前两个重载方法]之间模棱两可

static void M(Func<int> f) { }
static void M(Func<string> f) { }
static void M(Func<dynamic> f) { }

static dynamic DynamicObject() {
    return new object();
}

static void Test() {
    M(() => 0);
    M(() => "");
    M(() => DynamicObject()); //doesn't compile
}
  1. 为什么由于类型不是静态已知的,它不能解析为接受dynamic的重载?
  2. 重载方法是否可能使用dynamic
  3. 解决这个问题的最佳方法是什么?

你不觉得使用一个泛型会更好吗?现在这样仍然是模棱两可的(请记住,动态类型可以是A也可以是B,编译器如何知道调用哪个?)。但如果你想的话,我认为你可以将方法改为static void M<T>(Func<DataTime, T> f) where T : Astatic void M<T>(Func<DataTime, T> f) where T : B,我认为这样就允许重载了?- 当然,在“Test”中的所有方法调用都应该没问题? - Smudge202
这个例子是人为制造的,但准确反映了我的困境,即如何将dynamic传递给一个重载的方法。最好的解决方案是什么? - Daniel
如上所述 - 不要。你如何重载一个可以是任何东西的对象? - Smudge202
@Smudge:当然可以有一个接受“object”的重载。请参考Tigran的工作示例。 - Daniel
1
不要那样做!使用泛型代替! - Peter Olson
显示剩余2条评论
2个回答

5
问题在于类型推断。编译器试图根据参数找出要使用哪个重载函数,但它也试图根据所选的重载函数找出参数的类型。对于 M(() => DynamicObject()) 这种情况,过程大致如下:
  1. 该方法的参数是一个没有参数的 lambda 表达式。这使我们有了三个可能的重载函数。
  2. lambda 表达式的主体返回 dynamic。因为从 dynamic 到任何其他类型都存在隐式转换,所以现在我们知道所有三个重载函数都是可行的。
  3. 尝试选择最佳重载函数。在大多数情况下,“最佳”意味着最派生的类型。因为 intstring 都派生自 object,所以具有 intstring 的重载函数被认为是最佳的。
  4. 现在我们有了两个“最佳”的重载函数,这意味着编译器实际上无法选择其中任何一个。编译失败。
现在,关于您问题的可能解决方案:
  1. Make the type of the lambda explicit, either using cast or typed local variable:

    M((Func<dynamic>)(() => DynamicObject()));
    

    or

    Func<dynamic> f = () => DynamicObject();
    M(f);
    
  2. Rename the dynamic overload to something like DynamicM. This way, you don't have to deal with overload resolution.

  3. This one feels somewhat wrong to me: make sure the dynamic overload is the only one that fits, by casting to object:

    M(() => (object)DynamicObject())
    

啊,我没有想到Func是逆变的。我认为dynamic/object处于逆变位置是问题的根源。我仍然想知道为什么它没有在可能匹配列表中包括dynamic重载。 - Daniel
@Daniel,这与逆变无关。如果Func不是逆变的话,同样的事情也会发生。编译器在第一次通过中确实包含了dynamic重载,但它没有将其包含在“最佳”匹配项之一。 - svick
明白了。你说得对,它会选择最具体的重载。我将第一个重载更改为接受 Func<A>,第二个重载更改为接受 Func<B>(其中 B 派生自 A),然后它解析为第二个重载。奇怪但却是真的。 - Daniel

1

根据MSDN的定义:

dynamic

类型dynamic在大多数情况下的行为类似于类型object。然而,包含类型为dynamic的表达式的操作不会被编译器解析或类型检查。编译器将有关操作的信息打包在一起,该信息稍后用于在运行时评估操作。作为过程的一部分,类型为dynamic的变量被编译成类型为object的变量。因此,类型dynamic仅存在于编译时,而不是运行时。

因此,在编译时不存在dynamic,因为它需要将其转换为目标*类型*,这就是为什么无法解析它的原因。什么是目标类型?

实际上,如果您执行以下操作:

static void M(Func<int> f) { }
static void M(Func<string> f) { }
static void M(Func<object> f) { } // could be also declared like dynamic here, works by the way

static object DynamicObject()
{
    return new object();
}

static void Test()
{
    M(() => 0);
    M(() => "");
    M(() => DynamicObject());
}

它完美地按照您的意愿工作,因为object已经在编译时作为一种类型存在,与需要进行转换dynamic类型不同。


你的例子恰好说明了我为什么认为它会起作用,因为 dynamic 只是 object。有没有办法让重载分辨能够使用 dynamic - Daniel
不是对象,它必须在编译时转换为某种类型,但在这种情况下无法理解它应该是哪种类型的目的地... - Tigran
好的,回到我的原始问题:有没有办法重载接受dynamic的方法,或者它永远无法解决? - Daniel
你难道不是在描述泛型提供的确切功能吗?你似乎卡在了“动态”这个词上,但那不是你要找的词吧? - Smudge202
在我的情况下,它必须是“dynamic”。我可以删除重载(但我不想这样做),但我无法消除“dynamic”。 - Daniel
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接