C#4.0中使用的"动态"类型是什么?

Fah*_*had 218 .net c# dynamic c#-4.0

C#4.0引入了一种名为"dynamic"的新类型.这听起来不错,但程序员会用它做什么?

有没有可以节省一天的情况?

ang*_*son 206

dynamic加入关键字,用C#4.0的许多其他新功能一起,以使其更简单谈谈代码,住在或来自其他运行时,有不同的API.

举个例子.

如果你有一个COM对象,比如Word.Application对象,并且想要打开一个文档,那么执行该操作的方法不少于15个参数,其中大多数是可选的.

要调用此方法,您需要这样的东西(我正在简化,这不是实际的代码):

object missing = System.Reflection.Missing.Value;
object fileName = "C:\\test.docx";
object readOnly = true;
wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing, ref missing, ref missing, ref missing,
    ref missing, ref missing);
Run Code Online (Sandbox Code Playgroud)

注意所有这些论点?你需要传递那些自C#之前的版本4.0之前没有可选参数的概念.在C#4.0中,通过引入以下内容使COM API变得更容易使用:

  1. 可选参数
  2. 制作ref可选的COM API的
  3. 命名参数

上述调用的新语法是:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Run Code Online (Sandbox Code Playgroud)

看看它看起来多么容易,它的可读性有多大?

让我们分开:

                                    named argument, can skip the rest
                                                   |
                                                   v
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
                                 ^                         ^
                                 |                         |
                               notice no ref keyword, can pass
                               actual parameter values instead
Run Code Online (Sandbox Code Playgroud)

神奇的是,C#编译器现在将注入必要的代码,并在运行时使用新类,几乎完成与之前完全相同的操作,但语法已被隐藏,现在您可以专注于什么,而不是如何.Anders Hejlsberg喜欢说你必须引用不同的"咒语",这是对整个事物的魔力的一种双关语,你通常需要挥动你的手并按照正确的顺序说出一些神奇的词语获得某种类型的咒语.与COM对象交谈的旧API方式很多,你需要跳过很多箍,以便哄骗编译器为你编译代码.

如果您尝试与没有接口或类的COM对象进行通信,那么在版本4.0之前的C#中会出现更多问题,您所拥有的只是一个IDispatch参考.

如果你不知道它是什么,IDispatch基本上是COM对象的反射.随着IDispatch你可以问对象接口"什么是被称为保存方法的身份证号码",并建立包含参数值的某些类型的数组,最后调用Invokeon方法IDispatch接口来调用方法,传递所有你设法收集到的信息.

上面的Save方法看起来像这样(这绝对不是正确的代码):

string[] methodNames = new[] { "Open" };
Guid IID = ...
int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid);
SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... });
wordApplication.Invoke(methodId, ... args, ...);
Run Code Online (Sandbox Code Playgroud)

所有这些只是打开一个文件.

很久以前,VB有可选的参数和对大多数开箱即用的支持,所以这个C#代码:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
Run Code Online (Sandbox Code Playgroud)

基本上只是C#在表达性方面赶上VB,但通过使其可扩展,而不仅仅是COM,以正确的方式做到这一点.当然,这也适用于VB.NET或基于.NET运行时构建的任何其他语言.

您可以IDispatch维基百科上找到有关界面的更多信息:IDispatch,如果您想了解更多相关信息.这真是血腥的东西.

但是,如果你想与Python对象交谈怎么办?与用于COM对象的API相比,它有一个不同的API,并且由于Python对象本质上也是动态的,因此您需要求助于反射魔法来找到正确的调用方法,它们的参数等,而不是.NET.反射,为Python编写的东西,非常像上面的IDispatch代码,只是完全不同.

而对于Ruby?还有一个不同的API.

JavaScript的?相同的交易,不同的API也是如此.

动态关键字包含两件事:

  1. C#中的新关键字, dynamic
  2. 一组运行时类,它们知道如何处理不同类型的对象,实现dynamic关键字所需的特定API ,并将调用映射到正确的处理方式.API甚至是文档化的,因此如果您有来自未涵盖的运行时的对象,则可以添加它.

dynamic但是,该关键字不是要替换任何现有的仅.NET代码.当然,你可以做到这一点,但由于这个原因没有添加,并且前面的Anders Hejlsberg的C#编程语言的作者最坚定的是他们仍然认为C#是一种强类型语言,并且不会牺牲那个原则.

这意味着虽然您可以编写如下代码:

dynamic x = 10;
dynamic y = 3.14;
dynamic z = "test";
dynamic k = true;
dynamic l = x + y * z - k;
Run Code Online (Sandbox Code Playgroud)

并且让它编译,它并不意味着一种魔术 - 让我们想出你在运行时意味着什么样的系统.

整个目的是让它更容易与其他类型的对象交谈.

互联网上有很多关于关键词,支持者,反对者,讨论,咆哮,赞美等的资料.

我建议你从以下链接开始,然后谷歌更多:

  • +1*那*是`dynamic`的用途.谢谢. (38认同)
  • 除了COM之外,它对于Web JSON API也很有用,其中反序列化的JSON对象的结构未在C#中指定.例如[System.Web.Helpers.Json](http://msdn.microsoft.com/en-us/library/system.web.helpers.json.aspx)的Decode方法[返回一个动态对象](http ://stackoverflow.com/a/9495155/575530). (11认同)
  • 不得不说,非常好的答案,非常好解释 (4认同)
  • 我不同意他的观点,但这是一个观点问题,而不是事实问题。对我来说,“强类型”意味着编译器在编译时知道使用哪种类型,从而强制执行围绕这些类型设置的规则。对我来说,您可以选择推迟规则检查和绑定到运行时的动态类型这一事实并不意味着该语言是弱类型的。我通常不会将强类型与弱类型进行比较,但是,我通常将其与动态类型进行比较,例如 Python 等语言,在这种语言中,一切都是鸭子,直到它吠叫为止。 (2认同)

Pra*_*ana 178

dynamic关键字是C#4.0的新增内容,用于告诉编译器变量的类型可以更改,或者直到运行时才知道它.可以将其视为能够与Object进行交互而无需进行转换.

dynamic cust = GetCustomer();
cust.FirstName = "foo"; // works as expected
cust.Process(); // works as expected
cust.MissingMethod(); // No method found!
Run Code Online (Sandbox Code Playgroud)

请注意,我们不需要转换或声明cust类型为Customer.因为我们将其声明为动态,所以运行时接管然后搜索并为我们设置FirstName属性.当然,现在,当您使用动态变量时,您放弃了编译器类型检查.这意味着调用cust.MissingMethod()将编译,直到运行时才会失败.此操作的结果是RuntimeBinderException,因为未在Customer类上定义MissingMethod.

上面的示例显示了调用方法和属性时动态的工作原理.另一个强大(并且具有潜在危险)的功能是能够为不同类型的数据重用变量.我确信那里的Python,Ruby和Perl程序员可以想出一百万种方法来利用这一点,但我一直在使用C#,以至于它对我来说感觉"错误".

dynamic foo = 123;
foo = "bar";
Run Code Online (Sandbox Code Playgroud)

好的,所以你很可能不会经常像上面那样编写代码.但是,有时候,变量重用可以派上用场,或者清理一堆脏代码.我经常遇到的一个简单案例是不断地在十进制和双精度之间进行转换.

decimal foo = GetDecimalValue();
foo = foo / 2.5; // Does not compile
foo = Math.Sqrt(foo); // Does not compile
string bar = foo.ToString("c");
Run Code Online (Sandbox Code Playgroud)

第二行不编译,因为2.5被输入为double而第3行没有编译,因为Math.Sqrt需要一个double.显然,您所要做的就是强制转换和/或更改变量类型,但可能存在动态使用的情况.

dynamic foo = GetDecimalValue(); // still returns a decimal
foo = foo / 2.5; // The runtime takes care of this for us
foo = Math.Sqrt(foo); // Again, the DLR works its magic
string bar = foo.ToString("c");
Run Code Online (Sandbox Code Playgroud)

阅读更多功能:http://www.codeproject.com/KB/cs/CSharp4Features.aspx

  • 就个人而言,我不喜欢在c#中使用`dynamic`来解决可以通过标准c#特性和静态类型解决的问题(甚至更好),或者最多使用类型推断(`var`).当涉及到DLR的互操作性问题时,应该使用`dynamic`.如果用静态类型语言编写代码,比如c#is,那就去做,不要模仿动态语言.多数民众赞成只是丑陋. (95认同)
  • 如果你在你不需要它们的代码中大量使用`dynamic`变量(比如在你的squareroot的例子中),你放弃了干净的编译时错误检查; 相反,您现在可能会遇到运行时错误. (40认同)
  • 很好,但有几个小错误.首先,说动态意味着*变量的*类型可以改变是不正确的.有问题的变量是"动态"类型(从C#语言的角度来看;从CLR的角度来看,变量是对象类型).变量的类型*从不*改变.变量的*value*的运行时类型可以是与变量类型兼容的任何类型.(或者在引用类型的情况下,它可以为null.) (32认同)
  • 我在这方面花了一点点,主要是因为它隐含地主张将关键字用于一般用途.它具有特定目标(在Lasses的回答中完美描述),尽管这个答案在技术上是正确的,但它可能会导致开发人员误入歧途. (18认同)
  • 关于你的第二点:C#已经具有"创建一个可以放入任何内容的变量"的功能 - 你总是可以创建一个类型为object的变量.动态的有趣之处在于你在第一段中指出的内容:动态与对象几乎完全相同,除了语义分析延迟到运行时,语义分析在表达式的运行时类型上完成.(主要是.有一些例外.) (15认同)
  • 现在我们所需要的只是在"不安全"之外获取指针,最后我们可以再次编写C(++)代码. (5认同)

Ste*_*dis 27

我很惊讶没人提到多次派遣.解决这个问题的常用方法是通过访问者模式,但这并不总是可行的,因此您最终会进行堆叠is检查.

所以这是我自己应用的真实例子.而不是做:

public static MapDtoBase CreateDto(ChartItem item)
{
    if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item);
    if (item is MapPoint) return CreateDtoImpl((MapPoint)item);
    if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item);
    //other subtypes follow
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}
Run Code Online (Sandbox Code Playgroud)

你做:

public static MapDtoBase CreateDto(ChartItem item)
{
    return CreateDtoImpl(item as dynamic);
}

private static MapDtoBase CreateDtoImpl(ChartItem item)
{
    throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType());
}

private static MapDtoBase CreateDtoImpl(MapPoint item)
{
    return new MapPointDto(item);
}

private static MapDtoBase CreateDtoImpl(ElevationPoint item)
{
    return new ElevationDto(item);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在第一种情况下ElevationPoint是子类,MapPoint如果它没有放在它之前 MapPoint永远不会到达.动态不是这种情况,因为将调用最接近的匹配方法.

正如您可能从代码中猜到的那样,当我从ChartItem对象执行到可序列化版本的转换时,该功能非常方便.我不想让访问者污染我的代码,我也不想ChartItem用无用的序列化特定属性来污染我的对象.

  • 那么你可以选择与C#7进行模式匹配,不是吗? (4认同)
  • downvoter是否需要解释?我保证不会报复人...... (3认同)
  • @Kugel这是真的,但我不会称之为_hack_.静态分析是好的,但我不会让它阻止我从一个优雅的解决方案,其中的替代方案是:开放 - 封闭原则违规(访客模式)或增加的圈复杂性与可怕的`是`堆叠在一起的另一个. (2认同)
  • 好吧,那样的话,操作员的成本要便宜得多(避免重复铸造),并且您可以获得静态分析(-)和性能。 (2认同)

Phi*_*ier 11

它使静态类型语言(CLR)更容易与在DLR(动态语言运行时)上运行的动态类型(python,ruby ...)进行互操作,请参阅MSDN:

例如,您可以使用以下代码在C#中增加XML中的计数器.

Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1);
Run Code Online (Sandbox Code Playgroud)

通过使用DLR,您可以使用以下代码代替相同的操作.

scriptobj.Count += 1;
Run Code Online (Sandbox Code Playgroud)

MSDN列出了这些优点:

  • 简化将动态语言移植到.NET Framework的过程
  • 启用静态类型语言中的动态功能
  • 提供DLR和.NET Framework的未来优势
  • 实现库和对象的共享
  • 提供快速动态调度和调用

有关详细信息,请参阅MSDN.

  • @Dykam:虚拟机没有变化.DLR在返回.NET 2.0的过程中运行良好. (2认同)

Akl*_*kli 6

使用示例:

您使用了许多具有公共属性 'CreationDate' 的类:

public class Contact
{
    // some properties

    public DateTime CreationDate { get; set; }        
}

public class Company
{
    // some properties

    public DateTime CreationDate { get; set; }

}

public class Opportunity
{
    // some properties

    public DateTime CreationDate { get; set; }

}
Run Code Online (Sandbox Code Playgroud)

如果您编写一个公共方法来检索 'CreationDate' 属性的值,则必须使用反射:

    static DateTime RetrieveValueOfCreationDate(Object item)
    {
        return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
    }
Run Code Online (Sandbox Code Playgroud)

使用“动态”概念,您的代码更加优雅:

    static DateTime RetrieveValueOfCreationDate(dynamic item)
    {
        return item.CreationDate;
    }
Run Code Online (Sandbox Code Playgroud)

  • 鸭子打字,不错。但是,如果这些是您的类型,您应该为此使用接口。 (8认同)