【问题标题】:dynamic and generics in C#C#中的动态和泛型
【发布时间】:2013-04-19 00:44:47
【问题描述】:

正如在 C 3.5 中发现的那样,由于类型擦除,以下内容是不可能的:-

int foo<T>(T bar)
{
    return bar.Length; // will not compile unless I do something like where T : string
}

foo("baz");

我相信这在 C# 和 java 中不起作用的原因是由于一个称为类型擦除的概念,请参阅http://en.wikipedia.org/wiki/Type_erasure

阅读了dynamic 关键字后,我写了以下内容:-

int foo<T>(T bar)
{
    dynamic test = bar;
    return test.Length;
}

foo("baz"); // will compile and return 3

所以,据我了解,动态会绕过编译时检查,但如果类型已被删除,除非它更深入并使用某种反射,否则它肯定仍然无法解析符号?

以这种方式使用动态关键字是不好的做法吗?这会使泛型更强大吗?

【问题讨论】:

  • 类型没有被删除,你可以手动测试barstring并进行相应的转换。泛型限制为已知的最低类型。 T 无约束就是 object。转换为 dynamic 将使用 DLR 将任何成员检查移动到运行时,这实际上是鸭式打字。
  • 使用动态是不错的做法,您只是将转换推送到运行时,但您需要检查转换异常
  • 可能的答案here

标签: c# c#-4.0 generics


【解决方案1】:

动态和泛型是两个完全不同的概念。如果您想要编译时安全和速度,请使用强类型(泛型或仅标准 OOP 技术,例如继承或组合)。如果您在编译时不知道类型,您可以使用动态,但它们会更慢,因为它们使用运行时调用并且不太安全,因为如果类型没有实现您尝试调用的方法,您将收到运行时错误。

这两个概念不可互换,根据您的具体要求,您可以使用其中一个。

当然拥有下面的泛型约束是完全没用的,因为string是密封类型,不能作为泛型约束:

int foo<T>(T bar) where T : string
{
    return bar.Length;
}

你宁愿有这个:

int foo(string bar)
{
    return bar.Length;
}

【讨论】:

  • 我会说它完全没用,因为stringsealed 类:)
  • 其实它更没用,因为string不是一个有效的约束;)
  • 好人。我已经更新了我的答案,以考虑您的意见。
  • 字符串只是一个例子,但是是的,编译时安全是我忽略的;我想在 C++ 中,类型检查实际上是在编译时实现的,这实现了我对动态所做的工作
【解决方案2】:

在这种情况下,您不会以任何方式从动态中受益。您只需延迟错误,因为如果动态对象不包含 Length 属性,则会引发异常。如果以通用方法访问 Length 属性,我看不出任何理由不将其限制为肯定具有此属性的类型。

【讨论】:

    【解决方案3】:

    不,C# 没有类型擦除 - 只有 Java 有。

    但是如果你只指定 T,没有任何约束,你就不能使用 obj.Lenght,因为 T 实际上可以是任何东西。

    foo(new Bar());
    

    以上将解析为 Bar-Class,因此 Lenght 属性可能不可用。 只有当你确保 T 这个方法也确实有时,你才能在 T 上使用方法。 (这是通过 where 约束完成的。)

    使用动态,您可以放松编译时间检查,我建议您不要使用它们来破解泛型。

    【讨论】:

      【解决方案4】:

      "Dynamics 是一个强大的新工具,它使与动态语言以及 COM 的互操作变得更容易,并且可以用来替换很多笨重的反射代码。它们可以用来告诉编译器对一个对象执行操作,检查其中延迟到运行时。

      最大的危险在于在不适当的上下文中使用动态对象,例如在静态类型的系统中,或者更糟的是,在正确类型的系统中代替接口/基类。”

      Qouted From Article

      【讨论】:

        【解决方案5】:

        我相信这在 C# 和 java 中不起作用的原因是由于一个称为类型擦除的概念,请参阅http://en.wikipedia.org/wiki/Type_erasure

        不,这不是因为类型擦除。无论如何,C# 中没有类型擦除(与 Java 不同):运行时为每组不同的类型参数构造一个不同的类型,不会丢失信息。

        之所以不起作用是因为编译器对T一无所知,所以只能假设T继承自object,所以只有object的成员可用。但是,您可以通过在 T 上添加约束来向编译器提供更多信息。例如,如果您有一个带有 Length 属性的接口 IBar,则可以添加如下约束:

        int foo<T>(T bar) where T : IBar
        {
            return bar.Length;
        }
        

        但是如果你希望能够传递一个数组或一个字符串,它就行不通,因为Length 属性没有在StringArray 实现的任何接口中声明。 .

        【讨论】:

        【解决方案6】:

        我想我会权衡一下这个,因为没有人澄清泛型是如何“在引擎盖下”工作的。上面提到了 T 是一个对象的概念,并且非常清楚。没有讨论的是,当我们编译 C# 或 VB 或任何其他受支持的语言时, - 在中间语言 (IL) 级别(我们编译到的)更类似于汇编语言或等效于 Java 字节码, - 在这个级别,没有泛型!所以新的问题是如何在 IL 中支持泛型?对于访问泛型的每种类型,都会生成一个非泛型版本的代码,它将泛型(例如无处不在的 T)替换为调用它的实际类型。因此,如果您只有一种泛型类型,例如 List,那么这就是 IL 将包含的内容。但是,如果您使用泛型的许多实现,则会创建许多特定的实现,并且对原始代码的调用会替换为对特定非泛型版本的调用。需要明确的是,用作:new MyList() 的 MyList 将在 IL 中替换为 MyList_string() 之类的东西。

        这是我对正在发生的事情的(有限)理解。关键是,这种方法的好处在于,繁重的工作是在编译时完成的,并且在运行时不会降低性能 - 这也是为什么泛型可能如此受.NET 开发人员在任何地方和任何地方使用的原因。

        在不利的一面?如果一个方法或类型被多次使用,那么输出的程序集(EXE 或 DLL)将变得越来越大,这取决于相同代码的不同实现的数量。鉴于 DLL 输出的平均大小 - 我怀疑您是否会认为泛型是一个问题。

        【讨论】:

          猜你喜欢
          • 2017-02-09
          • 2012-10-14
          • 1970-01-01
          • 2012-01-20
          • 2014-01-11
          • 2011-08-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多