c# 4.0引入了一种叫做“动态”的新类型。这听起来不错,但是程序员要用它来做什么呢?

在某种情况下,它是否可以挽救局面?


当前回答

你可以使用pythonnet调用动态语言,比如CPython:

充满活力。

在对泛型应用数值运算符时,可以将泛型强制转换为动态泛型。这提供了类型安全性并避免了泛型的限制。这本质上是*鸭子打字:

T y = x *(动态)x,其中typeof(x)为T

其他回答

用法示例:

你使用了许多具有共同属性'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; }
    
}

如果你写一个通用的方法来检索'CreationDate'属性的值,你必须使用反射:

static DateTime RetrieveValueOfCreationDate(Object item)
{
    return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item);
}

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

static DateTime RetrieveValueOfCreationDate(dynamic item)
{
    return item.CreationDate;
}

动态关键字与c# 4.0的许多其他新特性一起被添加,以简化与其他运行时中或来自其他运行时的代码的对话,这些代码具有不同的api。

举个例子。

如果你有一个COM对象,比如Word。应用程序对象,并且想要打开一个文档,这样做的方法有不少于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);

注意到所有这些论点了吗?你需要传递这些参数,因为c# 4.0之前没有可选参数的概念。在c# 4.0中,COM api通过引入:

可选参数 COM api的ref是可选的 命名参数

上述调用的新语法将是:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

看看它看起来有多简单,可读性有多强吧?

让我们分开来看:

                                    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

The magic is that the C# compiler will now inject the necessary code, and work with new classes in the runtime, to do almost the exact same thing that you did before, but the syntax has been hidden from you, now you can focus on the what, and not so much on the how. Anders Hejlsberg is fond of saying that you have to invoke different "incantations", which is a sort of pun on the magic of the whole thing, where you typically have to wave your hand(s) and say some magic words in the right order to get a certain type of spell going. The old API way of talking to COM objects was a lot of that, you needed to jump through a lot of hoops in order to coax the compiler to compile the code for you.

在版本4.0之前的c#中,如果你试图与一个没有接口或类的COM对象对话,你所拥有的只是一个IDispatch引用,事情就会变得更加糟糕。

如果你不知道它是什么,IDispatch基本上是COM对象的反射。使用IDispatch接口,您可以询问对象“称为Save的方法的id号是什么”,并构建包含参数值的特定类型的数组,最后在IDispatch接口上调用Invoke方法来调用该方法,将设法收集到的所有信息传递到一起。

上面的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, ...);

所有这些都是为了打开一个文档。

VB在很久以前就有可选参数,并支持大部分开箱即用的参数,所以下面的c#代码:

wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);

基本上就是c#在表现力方面赶上了VB,但是用了正确的方式,通过使它可扩展,而不仅仅是COM。当然,这在VB中也是可用的。NET或任何其他构建在.NET运行时之上的语言。

如果您想了解更多关于IDispatch接口的信息,可以在Wikipedia: IDispatch上找到。这真的很血腥。

但是,如果您想与Python对象对话呢?有一个不同于用于COM对象的API,因为Python对象本质上也是动态的,你需要求助于反射魔法来找到要调用的正确方法,它们的参数等等,但不是。net反射,一些为Python编写的东西,很像上面的IDispatch代码,只是完全不同。

鲁比呢?仍然是不同的API。

JavaScript ?同样的操作,不同的API。

dynamic关键字由两部分组成:

c#中的新关键字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;

让它编译,它并不是一种“让我们在运行时弄清楚你想要什么”类型的系统。

整个目的是使与其他类型的对象的对话变得更容易。

互联网上有很多关于关键字的材料,支持者,反对者,讨论,咆哮,赞扬等等。

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

DevDays 2010: Anders Hejlsberg - c# 4.0及更高版本 频道9:Mads Torgersen - c# 4.0内部:动态类型+ + DevX: COM互操作在c# 4.0中得到了更好的改进 Scott Hanselman——c# 4和动态关键字——关于。net 4(和Visual Studio 2010) Beta 1的旋风之旅

它在运行时求值,所以你可以像在JavaScript中那样切换类型到你想要的任何类型。这是正确的:

dynamic i = 12;
i = "text";

所以你可以根据需要改变类型。把它作为最后的手段;这是有益的,但我听说在生成IL的场景下发生了很多事情,这可能以性能代价为代价。

动态类型的另一个用例是遇到协方差或逆变问题的虚拟方法。一个这样的例子是臭名昭著的Clone方法,该方法返回与调用它的对象类型相同的对象。动态返回并不能完全解决这个问题,因为它绕过了静态类型检查,但至少你不需要像使用普通对象一样一直使用丑陋的类型转换。换句话说,类型转换是隐式的。

public class A
{
    // attributes and constructor here
    public virtual dynamic Clone()
    {
        var clone = new A();
        // Do more cloning stuff here
        return clone;
    }
}

public class B : A
{
    // more attributes and constructor here
    public override dynamic Clone()
    {
        var clone = new B();    
        // Do more cloning stuff here
        return clone;
    }
}    

public class Program
{
    public static void Main()
    {
        A a = new A().Clone();  // No cast needed here
        B b = new B().Clone();  // and here
        // do more stuff with a and b
    }
}

我很惊讶没有人提到多重调度。解决这个问题的通常方法是通过访问者模式,这并不总是可能的,所以你最终会得到堆叠的支票。

这是我自己的一个应用实例。而不是做:

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());
}

你该怎么做:

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);
}

注意,在第一种情况下,ElevationPoint是mappointment的子类,如果它没有放在mappointment之前,它将永远不会被到达。dynamic则不是这样,因为最接近的匹配方法将被调用。

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