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

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


COM互操作。尤其是IUnknown。这是专门为它设计的。


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

dynamic i = 12;
i = "text";

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


dynamic关键字是c# 4.0的新特性,用于告诉编译器变量的类型可以改变,或者直到运行时才知道。可以认为它可以与对象交互,而不必强制转换对象。

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

注意,我们既不需要强制转换也不需要声明cust为Customer类型。因为我们将它声明为动态的,所以运行时将接管它,然后为我们搜索并设置FirstName属性。当然,当你使用动态变量时,你就放弃了编译器类型检查。这意味着调用cast . missingmethod()将被编译,直到运行时才会失败。这个操作的结果是一个RuntimeBinderException,因为MissingMethod没有在Customer类上定义。

上面的例子展示了动态在调用方法和属性时是如何工作的。另一个强大的(潜在的危险)特性是能够为不同类型的数据重用变量。我相信Python、Ruby和Perl程序员可以想出一百万种方法来利用这一点,但我使用c#太久了,我觉得它“不对”。

dynamic foo = 123;
foo = "bar";

好吧,所以你很可能不会经常写上面这样的代码。然而,有时变量重用可以派上用场,或者可以清理遗留代码的脏部分。我经常遇到的一个简单的情况是必须不断地在小数和双精度之间进行强制转换。

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

第二行不能编译,因为2.5被输入为double类型,第3行不能编译,因为Math。根特期望双倍。显然,您所要做的就是强制转换和/或更改变量类型,但可能在某些情况下使用动态是有意义的。

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

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


它使静态类型语言(CLR)更容易与运行在DLR(动态语言运行库)上的动态语言(python, ruby…)互操作,参见MSDN:

例如,您可以使用下面的代码来增加一个计数器 在XML和c#中。 Scriptobj。SetProperty("Count", ((int)GetProperty("Count")) + 1); 通过使用DLR,可以使用下面的代码来代替 同样的操作。 scriptobj。计数+= 1;

MSDN列出了以下优点:

简化将动态语言移植到。net框架 在静态类型语言中启用动态特性 提供DLR和.NET框架的未来好处 启用库和对象的共享 提供快速动态分派和调用

详情请参阅MSDN。


动态关键字与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的旋风之旅


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

充满活力。

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

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


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

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

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对象。


用法示例:

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

The best use case of dynamic type variables for me was when, recently, I was writing a data access layer in ADO.NET (using SQLDataReader) and the code was invoking the already written legacy stored procedures. There are hundreds of those legacy stored procedures containing bulk of the business logic. My data access layer needed to return some sort of structured data to the business logic layer, C# based, to do some manipulations (although there are almost none). Every stored procedure returns different set of data (table columns). So instead of creating dozens of classes or structs to hold the returned data and pass it to the BLL, I wrote the below code which looks quite elegant and neat.

public static dynamic GetSomeData(ParameterDTO dto)
        {
            dynamic result = null;
            string SPName = "a_legacy_stored_procedure";
            using (SqlConnection connection = new SqlConnection("my connection string"))
            {
                SqlCommand command = new SqlCommand(SPName, connection);
                command.CommandType = System.Data.CommandType.StoredProcedure;                
                command.Parameters.Add(new SqlParameter("@empid", dto.EmpID));
                command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID));
                connection.Open();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        dynamic row = new ExpandoObject();
                        row.EmpName = reader["EmpFullName"].ToString();
                        row.DeptName = reader["DeptName"].ToString();
                        row.AnotherColumn = reader["AnotherColumn"].ToString();                        
                        result = row;
                    }
                }
            }
            return result;
        }

动态类型的另一个用例是遇到协方差或逆变问题的虚拟方法。一个这样的例子是臭名昭著的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
    }
}