在我看来,好像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);
我可以完全使用dump第一个方法,但由于这是一个性能非常敏感的库,并且大约75%的时间都会使用第一个方法,我宁愿不总是将数据包装在一个数组中并实例化一个如果只有一个项目,迭代器将覆盖foreach。
分裂成不同的命名方法最好是IMO。
思想?
编辑:
我想安德鲁可能会做点什么。
完整示例:
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,它可以工作,那么为什么它不适用于泛型呢?它正确地找到了两种可能的匹配方法,所以我认为根据规范,如果可能的话,它应该选择不使用params的方法。如果我在这里错了,请纠正我。
(不是)最终更新:
很抱歉带你们这个暴徒,我显然已经盯着这个太长时间......太多地看着仿制药和params一晚了。非泛型版本也会抛出模糊错误,我只是在模型测试中关闭了方法签名。
真正的最终更新:
好的,这就是为什么问题没有出现在我的非通用测试中。我使用“对象”作为类型参数。 SomeMethod(object)和SomeMethod(params object [])不会抛出模糊错误,我猜“null”会自动转换为“object”。我会说有点奇怪,但也许有点可以理解。
所以,奇怪的是,这个电话有效:
SomeMethod<object>("someMessage", null);
答案 0 :(得分:16)
在我看来,好像C#编译器中存在错误/不一致。
编译器肯定存在错误和不一致。 您还没有找到其中一个。编译器的行为完全正确并且在所有这些情况下都符合规范。
我正在尽力理解这个令人困惑的问题。让我试着把它分解成一系列问题。
为什么这会成功并调用第一种方法?
public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);
(推定:该项是除object []之外的编译时类型的表达式。)
重载分辨率必须在两种适用的方法之间进行选择。第二种方法仅适用于其扩展形式。仅适用于其扩展形式的方法自动比其正常形式中适用的方法更差。因此,选择了更好的方法。
为什么这会因歧义错误而失败?
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?
的类型由于该问题没有足够的信息来回答,我将代表您提出更好的问题。
为什么这会因歧义错误而失败?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", "hello");
没有。它成功。类型推断在两种方法中为T选择“string”。这两种通用方法都适用;第二种适用于其扩展形式,因此它会失败。
好的,那么为什么这会因模糊错误而失败?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", null);
没有。它失败了“无法推断T”错误。这里没有足够的信息来确定在任何一种情况下T是什么。由于类型推断无法找到候选方法,因此候选集是空的,并且重载决策没有任何选择。
所以这成功是因为......?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (string)null);
类型推断推断,当使用“string”构造时,两种方法都是候选方法。同样,第二种方法更糟糕,因为它仅适用于其扩展形式。
如果我们从图片中进行类型推断怎么办?为什么这个含糊不清?
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);
我们选择字符串版本,因为字符串比对象更具体。每个字符串都是一个对象,但不是每个对象都是一个字符串。
string不能转换为string []。 string []不能转换为字符串。两者都没有比另一个更具体。因此这是一个模棱两可的错误;有两个“最佳”候选人。
那为什么这会成功呢?它会做什么?
public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<object>("woohoo", null);
我们再次有三个候选人,而不是两个。我们像以前一样自动消除扩展形式,留下两个。正常形式的两种方法仍然存在,哪种更好?
我们必须确定哪个更具体。每个对象数组都是一个对象,但不是每个对象都是一个对象数组。 object []比object更具体,所以我们选择调用带有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();
}
现在每个案例都以合理的语义和体面的效率来处理。对于任何给定的呼叫站点,您可以立即准确地预测将调用哪种方法;只有三个可能性:一个参数,两个参数或两个以上的参数。每种方法都通过特定方法明确处理。
答案 1 :(得分: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
是什么类型。同样有效的是假设它是一个字符串或一个字符串数组,因此当你专门用它来表示它时,为什么它是不明确的而不是模糊的(即你告诉编译器应该如何处理它)。
<强>更新强>
“同样可以争辩 SomeMethod(字符串,字符串)和 SomeMethod(string,params string []), 但它有效“
实际上,不是没有。你得到了同样模糊的方法问题。
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
}
答案 2 :(得分:3)
您应该将签名更改为更具体。例如:
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种方法。没有歧义。
答案 3 :(得分:0)
我想为遇到此问题的任何人添加这个消息来解释SomeMethod(object)和SomeMethod(params object [])的模糊性问题。在Figo Fei的MSDN论坛上,我从C#规范中挖出了这个:
10.6.1.4参数数组
使用params修饰符声明的参数是参数数组。
执行重载决策时,带参数数组的方法可能以其正常形式或扩展形式(第7.4.3.1节)适用。只有当方法的正常形式不适用且且只有与扩展形式具有相同签名的方法尚未在同一类型中声明时,方法的扩展形式才可用。
当参数数组的类型是object []时,在方法的正常形式和单个对象参数的消耗形式之间出现潜在的歧义。歧义的原因是object []本身可以隐式转换为type对象。然而,歧义没有问题,因为如果需要,可以通过插入一个演员来解决它。