C#方法泛型参数bug参数bug?

Mik*_*e M 6 c# generics parameters

在我看来,好像C#编译器中存在错误/不一致.

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

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

// ....
SomeMethod("woohoo", item);
Run Code Online (Sandbox Code Playgroud)

然而,这导致"以下方法之间的调用不明确"错误:

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

// ....
SomeMethod("woohoo", (T)item);
Run Code Online (Sandbox Code Playgroud)

我可以完全使用转储第一个方法,但由于这是一个性能非常敏感的库,并且第一个方法将在大约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
    }
}
Run Code Online (Sandbox Code Playgroud)

我仍然认为演员阵容很奇怪 - 这个电话并不含糊.如果用字符串或对象或任何非泛型类型替换T,它可以工作,那么为什么它不适用于泛型呢?它正确地找到了两种可能的匹配方法,所以我认为根据规范,如果可能的话,它应该选择不使用params的方法.如果我在这里错了,请纠正我.

(不是)最终更新:

很抱歉带你们这个暴徒,我显然已经盯着这个太长时间了......太多地看着仿制药和params一晚了.非泛型版本也会抛出模糊错误,我只是在模型测试中关闭了方法签名.

真正的最终更新:

好的,这就是为什么问题没有出现在我的非通用测试中.我使用"对象"作为类型参数.SomeMethod(object)和SomeMethod(params object [])不会抛出模糊错误,我猜"null"会自动转换为"object".我会说有点奇怪,但也许有点可以理解.

所以,奇怪的是,这个电话有效:

SomeMethod<object>("someMessage", null);
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 17

在我看来,好像C#编译器中存在错误/不一致.

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

我正在尽力理解这个令人困惑的问题.让我试着把它分解成一系列问题.

为什么这会成功并调用第一种方法?

public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);
Run Code Online (Sandbox Code Playgroud)

(推定:该项是除object []之外的编译时类型的表达式.)

重载决策必须在两种适用的方法之间进行选择 第二种方法仅适用于其扩展形式.仅适用于其扩展形式的方法自动比其正常形式中适用的方法更差.因此,选择了更好的方法.

为什么这会因模糊错误而失败?

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

这是不可能的,因为你没有说"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");
Run Code Online (Sandbox Code Playgroud)

它没有.它成功了.类型推断在两种方法中为T选择"string".这两种通用方法都适用; 第二种适用于其扩展形式,因此它失败了.

好的,那么为什么这会因模糊错误而失败?

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

它没有.它失败了"无法推断T"错误.在这两种情况下,没有足够的信息来确定T是什么.由于类型推断无法找到候选方法,因此候选集是空的,并且重载决策之间没有任何选择.

所以这成功是因为......?

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

类型推断推断,当使用"string"构造时,两种方法都是候选方法.同样,第二种方法更糟糕,因为它仅适用于其扩展形式.

如果我们从图片中取出类型推断怎么办?为什么这个含糊不清?

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

我们现在有三个适用的候选人.第一种方法,正常形式的第二种方法,以及扩展形式的第二种方法.扩展形式被丢弃,因为扩展比正常情况更糟.这留下了两种正常形式的方法,一种采用字符串,另一种采用字符串[].哪个更好?

当面对这种选择时,我们总是选择具有更具体类型的那种.如果你说

public void M(string s) { ... }
public void M(object s) { ... }
...
M(null);
Run Code Online (Sandbox Code Playgroud)

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

string不能转换为string [].string []不能转换为字符串.两者都没有比另一个更具体.因此这是一个模棱两可的错误; 有两个"最佳"候选人.

那为什么这成功了,它做了什么?

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

我们再次有三个候选人,而不是两个.我们像以前一样自动消除扩展形式,留下两个.正常形式的两种方法仍然存在,哪种更好?

我们必须确定哪个更具体.每个对象数组都是一个对象,但并非每个对象都是一个对象数组.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();
}
Run Code Online (Sandbox Code Playgroud)

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


Pao*_*olo 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

编辑

上面代码的问题是编译器无法判断类型null是什么。假设它是一个字符串或字符串数​​组也是同样有效的,因此为什么当它只是 null 时它是不明确的,而当你专门转换它时它是不明确的(即你告诉编译器应该如何处理它)。

更新

“同样可以争论 SomeMethod(string, string) 和 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
}
Run Code Online (Sandbox Code Playgroud)