【问题标题】:C# generics not recognizing typeC# 泛型无法识别类型
【发布时间】:2013-01-02 22:38:01
【问题描述】:

我不明白为什么下面的代码会返回 Cannot resolve method Write(T) - 这对我来说似乎很明确:

    private static void WriteToDisk<T>(string fileName, T[] vector)
    {
        using (var stream = new FileStream(fileName, FileMode.Create))
        {
            using (var writer = new BinaryWriter(stream))
            {
               foreach(T v in vector) writer.Write(v);
                writer.Close();
            }
        }
    }

我想定义一个通用的二进制写入方法,可以处理向量,例如int[]long[]double[]

【问题讨论】:

  • 旁注:你不需要关闭 writer,因为无论如何你都在处理它。
  • “这对我来说似乎很明确”?真的吗?如果你传入一个对象数组,它会选择哪个重载?
  • BinaryWriter.Write() 不接受对象作为参数。编译器可以预先知道调用方法的所有类型,并且可以预编译方法模板的一系列实现。这在 C# 中可用吗?
  • @ZeJibe 不,该功能不存在。泛型不是模板:blogs.msdn.com/b/ericlippert/archive/2009/07/30/…

标签: c# .net generics


【解决方案1】:

调用Write() 的哪个重载将在编译时确定,而不是在运行时确定。 BinaryWriter 需要Write(object) 重载(或Write&lt;T&gt;(T) 泛型重载)才能允许以这种方式调用该方法。这个错误(正确)表明它两者都没有。

您需要编写自己的包装方法,该方法接受object(或一般类型的参数)并检查其类型以确定要调用的BinaryWriter.Write() 重载。

【讨论】:

  • 不仅如此:它将在处理泛型定义时确定,而不是在使用泛型时确定(并且T 是已知的)。
【解决方案2】:

cdhowie 有正确的解释。重载解决发生在编译时,在这种情况下,对T 一无所知,因此不适用重载。

使用dynamic,重载解析发生在运行时。也许你可以使用:

writer.Write((dynamic)v)

由于装箱和重复的重载解析发生在运行时,它会有点慢。

编辑:如果由于某种原因您无权访问dynamic,您可以通过显式反射获得类似的行为:

private static void WriteToDisk<T>(string fileName, T[] vector)
{
    var correctMethod = typeof(BinaryWriter).GetMethod("Write", new[] { typeof(T), });
    if (correctMethod == null)
        throw new ArgumentException("No suitable overload found for type " + typeof(T), "T");
    using (var stream = new FileStream(fileName, FileMode.Create))
    {
        using (var writer = new BinaryWriter(stream))
        {
           foreach(var v in vector)
               correctMethod.Invoke(writer, new object[] { v, });
        }
    }
}

我不知道这比dynamic快还是慢。

在任何一种情况下,如果你不小心使用了BinaryWriter 不支持的类型T(例如DateTime),一切都会编译好,你只会在运行时发现你的错误(当代码运行)。请参阅 jam40jeff 的答案以获得更安全的解决方案,您可以通过将委托实例传递给方法来自己指定重载。

【讨论】:

  • 非常感谢 Jeppe - 它确实有效。经过快速的性能基准测试后,dynamic 的速度是显式类型的两倍(但您只需为所有类型编写一次代码!)。有没有办法告诉编译器自动生成相同的方法,但对于程序将使用的所有可能类型(并且应该在编译时知道?)可能是一种宏?
  • @ZeJibe 为了完整起见,我在答案中添加了更多信息。
  • @ZeJibe 在编译时不可能知道您的方法可能使用的“所有可能的类型”。类型系统是开放式的。即使您的方法是程序集内部的(因此您理论上可以证明哪些类型可以在编译时使用),总有可能通过反射调用您的方法并为其提供编译器没有的某种类型的对象占。
  • @cdhowie 好点——但他们在 C++ 中有它。当您必须处理预先不知道列类型的大型数据文件时,模板特别有用。在 C++ 中,您最终会编写一个模板化方法;不幸的是,在 C# 中,您必须为每种数据类型编写一个方法,或者使用这里讨论的泛型/动态(但随后您会失去性能,这对于大型数据集是难以接受的)。我们应该游说微软在 C# 中加入某种巧妙的模板吗?
  • @ZeJibe 在 C++ 中,模板实例化在编译时由 C++ 编译器编译。在 C# 中,泛型类实例化由运行时和/或 AOT 编译,由 C# 编译器编译。当运行时看到泛型类定义时,所有方法调用都已被 C# 编译器解析。在 C# 中实现模板意味着模板的源代码类(或一些新的字节码格式)必须存在于程序集中,否则在实例化模板时无法解析重载运算符之类的问题。
【解决方案3】:

我不同意dynamic 是最好的方法。这里的问题是你需要保证调用者传递T 可以处理的类型BinaryWriter.Write()。由于没有通用的类或接口可以通过约束T 来保证这一点,因此最好的方法是将责任“转嫁”给调用者,如下所示:

private static void WriteToDisk<T>(string fileName, T[] vector, Action<BinaryWriter, T> callWrite)
{
    using (var stream = new FileStream(fileName, FileMode.Create))
    {
        using (var writer = new BinaryWriter(stream))
        {
            foreach (T v in vector)
                callWrite(writer, v);
            writer.Close();
        }
    }
}

这样调用:

WriteToDisk("filename", new int[0], (w, o) => w.Write(o)); // compiles
WriteToDisk("filename", new string[0], (w, o) => w.Write(o)); // compiles
WriteToDisk("filename", new DateTime[0], (w, o) => w.Write(o)); // doesn't compile (as desired)

当然,如果只有一小部分已知类型,您可以这样创建“方便方法”:

private static void WriteToDisk(string fileName, int[] vector)
{
    WriteToDisk(fileName, vector, (w, o) => w.Write(o));
}

private static void WriteToDisk(string fileName, string[] vector)
{
    WriteToDisk(fileName, vector, (w, o) => w.Write(o));
}

现在你的电话很简单:

WriteToDisk("filename", new int[0]);
WriteToDisk("filename", new string[0]);

更多的代码,但更多的编译时类型安全和速度。

【讨论】:

    【解决方案4】:

    你可以改成:

    private static void WriteToDisk<T>(string fileName, T[] vector) where T : struct, IComparable,IComparable<T>
    

    如果要将数组类型保持为数值类型。

    【讨论】:

    • 好主意,但不足以编译。您确定约束的顺序吗?
    • @fex 扩展 BenVoigt 所说的内容,没有接受 IComparableIComparable&lt;T&gt;Write() 重载,所以这种技术根本没有帮助。
    猜你喜欢
    • 2022-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-26
    相关资源
    最近更新 更多