2010-05-18 50 views
4

我觉得好像在C#编译器中存在缺陷/不一致。C#方法通用参数参数错误?

这工作正常(第一种方法被调用):

public void SomeMethod(string message, object data); 
public void SomeMethod(string message, params object[] data); 

// .... 
SomeMethod("woohoo", item); 

然而,这会导致“的号召是以下方法之间的暧昧”的错误:

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 

// .... 
SomeMethod("woohoo", (T)item); 

我可以只使用转储第一种方法,但由于这是一个非常敏感的库,第一种方法将在大约75%的时间内使用,所以我宁愿不总是将东西包装在一个数组中,并实例化一个迭代器来遍历一个foreach,如果只有一个项目。

拆分成不同的命名方法最好是混乱的国际海事组织。

想法?

编辑:

我想安德鲁可能会到一些东西。

完整的示例:

public static class StringStuffDoer 
{ 
    public static string ToString<T>(T item1, T item2) 
    { 
     return item2.ToString() + item1.ToString(); 
    } 

    public static string ToString<T>(T item, params T[] items) 
    { 
     StringBuilder builder = new StringBuilder(); 

     foreach (T currentItem in items) 
     { 
      builder.Append(currentItem.ToString()); 
     } 

     return item.ToString() + builder.ToString(); 
    } 

    public static void CallToString() 
    { 
     ToString("someString", null); // FAIL 
     ToString("someString", "another string"); // SUCCESS 
     ToString("someString", (string)null); // SUCCESS 
    } 
} 

我仍然认为这是奇怪的是,需要投 - 呼叫一点也不含糊。它的工作原理是,如果用字符串或对象或任何非泛型类型替换T,那么为什么它不适用于泛型?它正确地找到了两种可能的匹配方法,所以我相信通过规范,它应该选择一个尽可能不使用参数的方法。如果我在这里错了,请纠正我。

(不那么)最后更新:

对不起,带你在这个tyraid家伙,我显然已经在这盯着太久......太多看仿制药,而params了一个晚上。非通用版本也会引发模糊错误,我只是在模型测试中关闭了方法签名。

REAL最后更新:

好吧,这就是为什么这个问题并没有在我的非通用测试显示出来。我使用“object”作为类型参数。 SomeMethod(object)和SomeMethod(params object [])不会抛出不明确的错误,我猜“null”会自动转换为“object”。我会说有点奇怪,但也许有点可以理解。

于是,奇怪的是,这个调用工作:

SomeMethod<object>("someMessage", null); 
+2

是item null? :s – 2010-05-18 09:40:00

+6

请发布一个简短但完整的程序来演示问题,并告诉我们您正在使用哪个版本的C#。 – 2010-05-18 09:43:42

+0

如果你不把物品投入到T中,它是如何工作的?如果在将项目声明为T型之前的某个时间点,它是否按照您的预期工作? – 2010-05-18 09:47:00

回答

3

似乎为我工作,你的代码如下所示的休息吗?

class TestThing<T> 
{ 
    public void SomeMethod(string message, T data) 
    { 
     Console.WriteLine("first"); 
    } 
    public void SomeMethod(string message, params T[] data) 
    { 
     Console.WriteLine("second"); 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     var item = new object(); 
     var test_thing = new TestThing<object>(); 
     test_thing.SomeMethod("woohoo", item); 
     test_thing.SomeMethod("woohoo", item, item); 

     Console.ReadLine(); 
    } 
} 

在.NET 3.5上编译得很好,运行时输出“first”,然后输出“second”。您使用哪个版本/定位?

编辑

你的代码中的问题上面是编译器不能告诉null是什么类型。假设它是一个字符串或一个字符串数组也是同样有效的,因此为什么只有在null时不明确,并且在你专门投射它时没有模棱两可。你告诉编译器应该如何处理它)。

UPDATE

"The same can be argued for SomeMethod(string, string) and SomeMethod (string, params string[]), yet it works"

其实,不,它不需要。你会得到相同的模糊方法问题。

public static string TypedToString(string item1, string item2) 
{ 
    return ""; 
} 

public static string TypedToString(string item1, params string[] items) 
{ 
    return ""; 
} 

public static void CallToString() 
{ 
    TypedToString("someString", null); // FAIL 
} 
+0

对不起,更新了我的帖子以反映问题。当参数值为null而没有强制转换时,行为不一致。 – 2010-05-18 10:12:36

+0

对于SomeMethod(string,string)和SomeMethod(string,params string [])也可以这样争论,但它起作用。 – 2010-05-18 10:19:42

+0

它正确地推断类型,因为它正在拾取两个匹配的方法并说“这两个匹配。”。问题是未能在通用情况下选择非参数方法的不一致性。只是在非泛型情况下选择非参数方法。 – 2010-05-18 10:25:38

2

您应该将签名更改为更具体。例如:

void Foo(object o1); 
void Foo(object o1, object o2); 
void Foo(object o1, object o2, object o3, params object[] rest); 

注意最后2这解决了模糊问题之间的区别(将仿制药的工作太)。

更新:

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, T data1, T data2, params T[] data); 

就2种方法。没有歧义。

+0

虽然我不应该写额外的方法。 SomeMethod(字符串,字符串)和SomeMethod(字符串,params字符串[])自己出来就好了。 – 2010-05-18 10:21:51

+0

你不需要额外的'Foo'方法,它只是一个例子。你仍然有一个模糊的方法调用,实际调用的是实现定义的,而且我不会讨论任何这样的行为。 – leppie 2010-05-18 10:23:48

+0

我不确定你在说什么 - 行为已定义良好 - 如果可能,请选择匹配的非参数方法。它在非泛型中这样做,即使我说SomeMethod(“string”,null),即使这在技术上是一个模糊的调用。不一致是这里唯一的问题。 – 2010-05-18 10:28:14

15

It appears to me as though there is a bug/inconsistency in the C# compiler.

编译器肯定存在错误和不一致之处。 您还没有找到其中之一。在所有这些情况下,编译器的行为都完全正确并且符合规范。

我正在尽我所能去理解这个非常混乱的问题。让我试着将其分解成一系列问题。

Why does this succeed and call the first method?

public void SomeMethod(string message, object data); 
public void SomeMethod(string message, params object[] data); 
// .... 
SomeMethod("woohoo", item); 

(推定:该项目是比对象[]以外的编译时类型的表达式。)

重载分辨率必须在两个应用的方法之间进行选择。第二种方法只适用于其扩展形式。仅适用于其扩展形式的方法自动比适用于其正常形式的方法更差。因此选择剩下的更好的方法。

Why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", (T)item); 

这是不可能说因为你不说什么“T”是。在这个例子中T来自哪里?有两个名为T声明的类型参数;这些代码是在这些方法之一的上下文中吗?既然这些是不同的类型都命名T可以有所作为。或者这是第三种称为T的类型?

由于问题没有足够的信息来回答,所以我会代表您提出一个更好的问题。

Why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", "hello"); 

事实并非如此。它成功了。类型推理在两种方法中均为T选择“字符串”。这两种通用方法都适用;第二个适用于其扩展形式,因此它失去了。

OK, then why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", null); 

事实并非如此。它以“不能推断T”的错误失败。这里没有足够的信息来确定T在哪种情况下。由于类型推断未能找到候选方法,因此候选集是空的,重载解析没有任何选择。

So this succeeds because... ?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", (string)null); 

类型推断推断,当以“串”构建这两种方法都是候选人。再次,第二种方法更糟,因为它只适用于其扩展形式。

What if we take type inference out of the picture? Why is this ambiguous?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod<string>("woohoo", null); 

我们现在有适用候选人。第一种方法,第二种方法的正常形式,第二种方法的扩展形式。由于扩展比正常情况差,因此放大的表单被丢弃。这使得两个方法以正常形式出现,一个采用字符串,另一个采用字符串[]。哪个更好?

当面对这个选择时,我们总是选择一个更具体的类型。如果你说

public void M(string s) { ... } 
public void M(object s) { ... } 
... 
M(null); 

,我们会选择字符串的版本,因为字符串比目标更具体。每个字符串都是一个对象,但不是每个对象都是一个字符串

字符串不能转换为字符串[]。字符串[]不能转换为字符串。两者都没有比其他更具体。因此这是一个模糊性错误;有两个“最佳”候选人。

Then why does this succeed and what does it do?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod<object>("woohoo", null); 

同样,我们有三个候选人,而不是两个。我们像之前一样自动消除扩展表单,留下两个。正常形式中剩下的两种方法哪个更好?

我们必须确定哪一个更具体。每个对象数组都是一个对象,但不是每个对象都是对象数组。 object []比对象更具体,因此我们选择调用接受对象[]的版本。我们传递一个null参数数组,几乎是肯定是不是你想要的。

这就是为什么这是一个极其糟糕的编程习惯,像你这样做超载。当你做这种事情时,你会为你的用户引入潜在的各种疯狂的歧义。 请不要设计这样的方法。

一个更好的办法来设计这种逻辑是:(请注意,我并没有实际编译这段代码,这只是我的头顶部)。

static string ToString<T>(T t) 
{ 
    return t == null ? "" : t.ToString(); 
} 
static string ToString<T>(T t1, T t2) 
{ 
    return ToString<T>(t1) + ToString<T>(t2); 
} 
static string ToString<T>(T t1, T t2, params T[] rest) 
{ 
    string firstTwo = ToString<T>(t1, t2); 
    if (rest == null) return firstTwo; 
    var sb = new StringBuilder(); 
    sb.Append(firstTwo); 
    foreach(T t in rest) 
     sb.Append(ToString<T>(t)); 
    return sb.ToString(); 
} 

现在每一个案件的处理具有合理的语义和体面的效率。对于任何给定的呼叫站点,您可以立即准确预测将调用哪种方法;只有三种可能性:一个论证,两个论证或两个以上的论证。每个都通过特定的方法明确处理。

+0

感谢您的非常完整的答案,但我是我从.NET框架本身有一个模式之后设计。 的String.Format(字符串,对象)和的String.Format(字符串,params对象[])时称为的String.Format( “格式”,NULL)在技术上是不明确的,但它需要的第一个。我明白为什么现在,但我的问题的根源是错误地认为行为也适用于除对象以外的其他类。它对对象起作用的唯一原因是对象[]可隐式转换为对象,但对其他类型显然不是这种情况。 – 2010-05-19 10:51:14

+0

@Mike:的确,我希望格式不是那样设计的;我觉得很困惑。但是,在这个宏伟的计划中,这是一个相当小的缺陷。 – 2010-05-19 13:51:40

0

我想为任何遇到此问题的人添加此小技巧来解释抛出我的SomeMethod(object)和SomeMethod(params object [])的歧义问题。这是从Figo Fei的MSDN论坛上的C#规范中挖出来的:

10.6.1。4参数数组

使用参数修饰符声明的参数是参数数组。

执行重载解析时,带有参数数组的方法可以以其正常形式或以其扩展形式(第7.4.3.1节)适用。只有当该方法的常规形式不适用且只有与展开形式具有相同签名的方法尚未在同一类型中声明时,方法的展开形式才可用。

当参数数组的类型是object []时,在该方法的正常形式和单个对象参数的扩展形式之间出现潜在的歧义。歧义的原因是对象[]本身可以隐式转换为对象类型。然而,歧义并不存在问题,因为如果需要的话,可以通过插入转换来解决。