Working with the Dynamic Type in C#

https://www.red-gate.com/simple-talk/dotnet/c-programming/working-with-the-dynamic-type-in-c/?utm_source=simpletalkdotnet&utm_medium=pubemail&utm_content=20181127-slota1&utm_term=simpletalkmain

by Camilo Reyes

15 October 2018

Dynamic types were introduced in .NET 4. Dynamic objects let you work with structures such as JSON documents whose composition may not be known until runtime. In this article, Camilo Reyes explains how to work with dynamic types.

The introduction of the dynamic keyword in .NET 4.0 brings a paradigm shift for C# programming. For C# programmers, dynamic behavior on top of a strong type system can feel wrong. It does seem like a step backward when you lose type safety during compilation.

Dynamic programming can leave you exposed to runtime errors. Declaring a dynamic variable that can mutate during execution is scary. Code quality suffers when developers make the wrong assumptions about the data.

For C# programmers, it is logical to avoid dynamic behavior in code. There are benefits to the classical approach of having strong types. Good feedback on data types through type checking is paramount to working programs. A good type system communicates intent and reduces ambiguity in code.

With the introduction of the Dynamic Language Runtime (DLR), what does this say about C#? .NET offers a rich type system useful for writing enterprise grade software. Let's take a closer look at the dynamic keyword and explore what it can do.

Type Hierarchy

Every type in the Common Language Runtime (CLR) inherits from System.Object. Now, read that last sentence again until you internalize this. This means the object type is the common parent to the entire type system. This fact alone aids us when we get to more exotic dynamic behavior. The idea here is to develop this 'code-sense', so you know how to navigate around dynamic types in C#.

To demo this, you can write the following program:

 

Console.WriteLine("long inherits from ValueType: " +

  typeof(long).IsSubclassOf(typeof(ValueType)));

I will omit using statements until the end of this article to keep code samples focused. Then, I will go over each namespace and what it does. This keeps me from having to repeat myself and provides an opportunity to review all types.

The code above evaluates to True inside the console. The long type in .NET is a value type, so it's more like an enumeration or a struct. The ValueType overrides the default behavior that comes from the object class. ValueType descendants go on the stack which have a short lifetime and are more efficient.

To validate that ValueType inherits from System.Object, do:

 

Console.WriteLine("ValueType inherits from System.Object: " +

  typeof(ValueType).IsSubclassOf(typeof(Object)));

This evaluates to True. This is an inheritance chain going back to System.Object. For value types, there are at least two parents in the chain.

Take a look at another C# type that descends from System.Object, for example:

 

Console.WriteLine("string inherits from System.Object: " +

  typeof(string).IsSubclassOf(typeof(Object)));

This code spits out True in the console. Another type that inherits from the object are reference types. Reference types get allocated on the heap and undergo garbage collection. The CLR manages reference types and deallocates them from the heap when necessary.

Look at the following figure so you can visualize the CLR's type system:

 
 

Both value and reference types are the basic building blocks of the CLR. This elegant type system predates both .NET 4.0 and dynamic types. I recommend keeping this figure in your mind's eye when you work with types in C#. So how does the DLR fit into this picture?

The Dynamic Language Runtime

The Dynamic Language Runtime (DLR) is a convenient way to work with dynamic objects. For example, say you have data as XML or JSON where the members aren't known ahead of time. The DLR lets you use natural code for working with objects and accessing members.

For C#, this enables working with libraries where types aren't known at compile time. A dynamic type eliminates magic strings in code for a natural API. This unlocks dynamic languages that sit on top of the CLR such as IronPython.

Think of the DLR as supporting three primary services:

  • Expression trees, which come from the System.Linq.Expressions namespace. The compiler generates expression trees at runtime which has dynamic language interoperability. Dynamic languages are outside the scope of this article, and I will not cover them here.
  • Call site caching, which is caching the results of dynamic operations. The DLR caches an operation like a + b and stores characteristics of a and b. When a dynamic operation executes, the DLR retrieves information available from previous operations.
  • Dynamic object interoperability are C# types you can use to access the DLR. These types include DynamicObject and ExpandoObject. There are more types available but pay attention to these two when working with the dynamic type.

To see how the DLR and CLR fit together, review this figure:

The DLR sits on top of the CLR. Recall that I said every type descends from System.Object. Well, I did scope it to the CLR but what about the DLR? Test this theory with this program:

 

Console.WriteLine("ExpandoObject inherits from System.Object: " +

  typeof(ExpandoObject).IsSubclassOf(typeof(Object)));

 
 

Console.WriteLine("DynamicObject inherits from System.Object: " +

  typeof(DynamicObject).IsSubclassOf(typeof(Object)));

Both ExpandoObject and DynamicObject evaluate to True in the command line. Think of these two as the basic building blocks for working with the dynamic type. This paints a clear picture of how both runtimes fit together.

A JSON Serializer

One problem the dynamic type solves is when you have a JSON HTTP request where members aren't known. Say there is this arbitrary JSON you want to work within C#. To solve for this, serialize this JSON into a C# dynamic type.

I'll use the Newtonsoft serializer, you can add this dependency through NuGet, for example:

 

dotnet add package Newtonsoft.Json –-version 11.0.2

You can use this serializer to work with both ExpandoObject and DynamicObject. Explore what each dynamic type brings to dynamic programming.

The ExpandoObject Dynamic Type

The ExpandoObject is a convenience type that allows setting and retrieving dynamic members. It implements IDynamicMetaObjectProvider which enables sharing instances between languages in the DLR. Because it implements IDictionary and IEnumerable, it works with types from the CLR. This allows an instance of the ExpandoObject to cast to IDictionary, for example. Then enumerate members like any other IDictionary type.

To use the ExpandoObject with an arbitrary JSON, you can write the following program:

 

var exObj = JsonConvert.DeserializeObject<ExpandoObject>(

  "{\"a\":1}") as dynamic;

 
 

Console.WriteLine($"exObj.a = {exObj?.a}, type of {exObj?.a.GetType()}");

This prints 1 and long in the console. Note that although it is a dynamic JSON, it binds to C# types in the CLR. Because the number type isn't known, the default serializer picks the biggest type which is a long. Note that I safely cast serializer results into a dynamic type with null checks. The reason is the serializer returns an object type from the CLR. Because ExpandoObject inherits from System.Object, it can be unboxed into a DLR type.

To be fancy, enumerate exObj with IDictionary:

 

foreach (var exObjProp in exObj as IDictionary<string, object>

  ?? new Dictionary<string, object>())

{

  Console.WriteLine($"IDictionary = {exObjProp.Key}: {exObjProp.Value}");

}

This prints IDictionary = a: 1 in the console. Be sure to use string and object as the key and value types. Otherwise, it will throw a RuntimeBinderException during the conversion.

The DynamicObject Dynamic Type

DynamicObject offers precise control over the dynamic type. You inherit from this type and override dynamic behavior. For example, you can define how to set and get dynamic members in the type. The DynamicObject lets you choose which dynamic operations to implement through overrides. This grants easier access than a language implementer which implements the IDynamicMetaObjectProvider. It is an abstract class, so it inherits from this instead of instantiating it. This class has 14 virtual methods which define dynamic operations on the type. Each virtual method allows overrides that specify dynamic behavior.

Say you want precise control over what gets into the dynamic JSON. Although you do not know the properties ahead of time, with a DynamicObject, you get control over the type.

Let's override three methods, TryGetMember, TrySetMember, and GetDynamicMemberNames:

 

public class TypedDynamicJson<T> : DynamicObject

{

  private readonly IDictionary<string, T> _typedProperty;

 
 

  public TypedDynamicJson()

  {

    _typedProperty = new Dictionary<string, T>();

  }

 
 

  public override bool TryGetMember(GetMemberBinder binder, out object result)

  {

    T typedObj;

 
 

    if (_typedProperty.TryGetValue(binder.Name, out typedObj))

    {

      result = typedObj;

 
 

      return true;

    }

 
 

    result = null;

 
 

    return false;

  }

 
 

  public override bool TrySetMember(SetMemberBinder binder, object value)

  {

    if (value.GetType() != typeof(T))

    {

      return false;

    }

 
 

    _typedProperty[binder.Name] = (T)value;

 
 

    return true;

  }

 
 

  public override IEnumerable<string> GetDynamicMemberNames()

  {

    return _typedProperty.Keys;

  }

}

C# generics strong type the _typedProperty in a generic way which drives member types. This means the property type comes from the T generic type. Dynamic JSON members are inside a dictionary and only store the generic type. This dynamic type allows for a homogeneous set of members of the same type. Although it allows a dynamic set of members, you can strongly type the behavior. Say you only care about long types from an arbitrary JSON:

 

var dynObj = JsonConvert.DeserializeObject<TypedDynamicJson<long>>(

  "{\"a\":1,\"b\":\"1\"}") as dynamic;

 
 

Console.WriteLine($"dynObj.a = {dynObj?.a}, type of {dynObj?.a.GetType()}");

 
 

var members = string.Join(",", dynObj?.GetDynamicMemberNames());

Console.WriteLine($"dynObj member names: {members}");

As a result, you'll see a single property with a value of 1 because the second property is a string type. If you change the generic type to a string, it will pick up the second property instead.

Type Results

Quite a bit of ground has been covered so far; here are some highlights:

  • All types from both CLR and DLR inherit from System.Object
  • The DLR is where all dynamic operations take place
  • ExpandoObject implements enumerable types from the CLR such as IDictionary
  • DynamicObject has precise control over the dynamic type through virtual methods

Look at the results captured in a console:

There Will Be Unit Tests

For unit tests, I'll use the xUnit test framework. In .NET Core, you add a test project with the dotnet new xunit command. One problem that becomes evident is mocking and verifying dynamic parameters. For example, say you want to verify that a method call exists with dynamic properties.

To use the Moq mock library, you can add this dependency through NuGet, for example:

 

dotnet add package Moq –-version 4.10.0

Say you have an interface and the idea is to verify it gets called with the right dynamic object:

 

public interface IMessageBus

{

  void Send(dynamic message);

}

Ignore what implements this interface. Those implementation details aren't necessary for writing unit tests. This will be the system under test:

 

public class MessageService

{

  private readonly IMessageBus _messageBus;

 
 

  public MessageService(IMessageBus messageBus)

  {

    _messageBus = messageBus;

  }

 
 

  public void SendRawJson<T>(string json)

  {

    var message = JsonConvert.DeserializeObject<T>(json) as dynamic;

 
 

    _messageBus.Send(message);

  }

}

You can make use of generics, so you can pass in the dynamic type for the serializer. Then call the IMessageBus and send the dynamic message. The method under test takes a string parameter and makes a call with a dynamic type.

For the unit tests, encapsulate it in a class MessageServiceTests. Begin by initializing mocks and the service under test:

 

public class MessageServiceTests

{

  private readonly Mock<IMessageBus> _messageBus;

  private readonly MessageService _service;

 
 

  public MessageServiceTests()

  {

    _messageBus = new Mock<IMessageBus>();

 
 

    _service = new MessageService(_messageBus.Object);

  }

}

The IMessageBus gets mocked using a C# generic in the Moq library. Then create a mock instance using the Object property. The private instance variables are useful inside all unit tests. Private instances with high reusability add class cohesion.

To verify a call with Moq, an intuitive approach is to try to do:

 

_messageBus.Verify(m => m.Send(It.Is<ExpandoObject>(

  o => o != null && (o as dynamic).a == 1)));

But alas, the error message you'll see is this: "An expression tree may not contain a dynamic operation." This is because C# lambda expressions do not have access to the DLR. It expects a type from the CLR which makes this dynamic parameter hard to verify. Remember your training and tap into your "code-sense" to solve this problem.

To navigate around what seems like a discrepancy between types, use a Callback method:

 

dynamic message = null;

 
 

_messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>()))

  .Callback<object>(o => message = o);

Note the callback gets typed to a System.Object. Because all types inherit from the object type, you're able to make the assignment into a dynamic type. C# can unbox the object inside the lambda expression into a dynamic message.

Time to write a nice unit test for the ExpandoObject type. Use xUnit as the testing framework, so you'll see the method with a Fact attribute.

 

[Fact]

public void SendsWithExpandoObject()

{

  // arrange

  const string json = "{\"a\":1}";

  dynamic message = null;

 
 

  _messageBus.Setup(m => m.Send(It.IsAny<ExpandoObject>()))

    .Callback<object>(o => message = o);

 
 

  // act

  _service.SendRawJson<ExpandoObject>(json);

 
 

  // assert

  Assert.NotNull(message);

  Assert.Equal(1, message.a);

}

Test with a DynamicObject type, reusing the TypedDymaicJson that you've seen before:

 

[Fact]

public void SendsWithDynamicObject()

{

  // arrange

  const string json = "{\"a\":1,\"b\":\"1\"}";

  dynamic message = null;

 
 

  _messageBus.Setup(m => m.Send(It.IsAny<TypedDynamicJson<long>>()))

    .Callback<object>(o => message = o);

 
 

  // act

  _service.SendRawJson<TypedDynamicJson<long>>(json);

 
 

  // assert

  Assert.NotNull(message);

  Assert.Equal(1, message.a);

  Assert.Equal("a", string.Join(",", message.GetDynamicMemberNames()));

}

Using C# generics, you can swap dynamic types for the serializer while reusing code. The Callback method in Moq allows you to make the necessary hop between type systems. Having an elegant type hierarchy with a common parent turns to be a lifesaver.

Using Statements

The following using statements are part of the code samples:

  • System: CLR's base types such as Object and Console
  • System.Collections.Generic: Enumerable types such as IDictionary
  • System.Dynamic: DLR's dynamic types such as ExpandoObject and DynamicObject
  • Newtonsonft.Json: JSON serializer
  • Moq: Mocking library
  • Xunit: Testing framework

Conclusion

The C# dynamic type may seem scary at first but has benefits on top of a strongly typed system. The DLR is where all dynamic operations occur and interoperate with the CLR. Type inheritance makes it easy to work with both type systems at the same time. In C#, there is no animosity between dynamic and static programming. Both type systems work together to solve dynamic problems in a creative way.

Working with the Dynamic Type in C#的更多相关文章

  1. 理解iOS 8中的Self Sizing Cells和Dynamic Type

    http://www.cocoachina.com/ios/20140922/9717.html 在iOS 8中,苹果引入了UITableView的一项新功能--Self Sizing Cells,对 ...

  2. Dynamic Type

    啥是 Dynamic Type 动态字体,即视力不好的用户,调整了默认字体的大小,开发者应该根据这个设置,动态改变界面的字体等,保证用户能看得清楚. 这个还是蛮重要的,视力不好的人越来越多. 用户在哪 ...

  3. iOS Programming Dynamic Type 2

    iOS Programming Dynamic Type  2       You will need to update two parts of this view controller for ...

  4. iOS Programming Dynamic Type 1

    iOS Programming Dynamic Type 1  Dynamic Type is a technology introduced in iOS 7 that helps realize ...

  5. Dynamic type checking and runtime type information

    动态类型的关键是将动态对象与实际类型信息绑定. See also: Dynamic programming language and Interpreted language Dynamic type ...

  6. Dynamic V Strongly Typed Views

    Come From https://blogs.msdn.microsoft.com/rickandy/2011/01/28/dynamic-v-strongly-typed-views/ There ...

  7. 关于type check的定义

    Concept: Type Checking There is no static type checking in Scheme; type checking is done at run time ...

  8. C# 动态语言特性,dynamic 关键字研究

    1       动态语言简介 支持动态特性的语言现在大行其道,并且有继续增长的趋势.比如 Ruby 和 Python, 还有天王级的巨星 --- JavaScript. 现在一个程序员说自己对 Jav ...

  9. Julia is a high-level, high-performance dynamic programming language for technical computing, with syntax that is familiar to users of other technical

    http://julialang.org/ julia | source | downloads | docs | blog | community | teaching | publications ...

随机推荐

  1. Python多进程vs多线程

    多任务的两种方式:多进程和多线程. 如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker. 如果用多线程实现Master-Worker,主线程就是Master, ...

  2. tcp粘包解决

    import socket import hashlib import subprocess import struct phone = socket.socket(socket.AF_INET,so ...

  3. 网络基础和python(二)

    一,五层协议 应用层    端口 传输层   tcp\udp 网络层   ipv4\6 数据链路层  ethernet 物理层    mac 二:什么是变量? 变量:核心在于变和量儿字,变->变 ...

  4. Web of Science API

    Web of Science API是通过Web Service获取Web of Science在线数据的应用程序接口,供各种编程语言调用.简单说,就是你能根据API实时.动态得到网页版Web of ...

  5. SQL注入之代码层防御

    [目录] 0x0 前言 0x1 领域驱动的安全 1.1 领域驱动的设计 1.2 领域驱动的安全示例 0x2 使用参数化查询 2.1 参数化查询 2.2 Java中的参数化语句 2.3 .NET(C#) ...

  6. 在html中使用thymeleaf编写通用模块

    在编写页面时,常常会需要用到通用模块,比如header部分.footer部分等. 项目前端使用的是themeleaf模板引擎,下面简单介绍下使用themeleaf写header通用模块: 1. 通用部 ...

  7. WPF c# 定时器

    //定时查询-定时器 DispatcherTimer dispatcherTimer = new DispatcherTimer(); dispatcherTimer.Tick += (s, e) = ...

  8. mysql 5.6 解压缩版安装教程

    MySQL 5.6 for Windows 解压缩版配置安装 听语音 | 浏览:68011 | 更新:2014-03-05 12:28 | 标签:mysql 1 2 3 4 5 6 7 分步阅读 My ...

  9. c++引用(修改引用的值)

    当我们希望修改某个函数的返回值时,通常我们会返回这个值的引用(因为函数返回值其实是返回那个值得一份拷贝而已,所以想要修改必须使用引用): .h文件 #pragma once #include < ...

  10. spring揭密学习笔记(2)-spring ioc容器:IOC的基本概念

    1. IoC的理念就是,让别人为你服务!2. 其实IoC就这么简单!原来是需要什么东西自己去拿,现在是需要什么东西就让别人送过来.一个生动的示例 3.三种依赖注入的方式 IoC模式最权威的总结和解释, ...