Saturday 1 July 2017

Generics and Dynamic

Time ago I saw somewhere a fragment of code that rather confused me. It was a line where a generic method was receiving dynamic as type parameter. "dynamic" is something at the C# compiler level, but at the MSIL level it does not exist, it just gets translated into callsite magic for accessing to its methods and properties. So, if you run this code:

  public static void DoSomething<T>()
  {
   Console.WriteLine("type: " + typeof(T).FullName);
  }
  
  static void Main(string[] args)
  {
    DoSomething<String>();
   
    DoSomething<Object>(); 
   
    DoSomething<dynamic>();
   
   //Output:
   // type: System.String
   // type: System.Object
   // type: System.Object
 }

You can see the passing dynamic or Object is just the same, in the method you are receiving T as an Object. If you decompile it and check the MSIL you'll see indeed the compiler is just replacing dynamic by Object in the call.

So from the point of view of the invoked method "dynamic" has no particular meaning, it's from the caller side where it can add semantic value to write DoSomething<dynamic> rather than DoSomething<Object>.

Another related case is when a method returns generic type of dynamic. For example, in Dapper we have this method:

public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction ...

Again, at the MSIL that dynamic is just an Object, you can check the MSIL:

Here the semantic value of using dynamic is particularly powerful. It's telling you that the method is returning an object that whatever its type, contains all the fields that you expect and so you can access them without a problem. You can consume it like this:

var rows = connection.Query(...); 

If they had just signed the method as returning IEnumerable<Object> you could write something like this:

IEnumerable<dynamic> rows = connection.Query(...); 

But obviously it's pretty much more semantic to have expressed this "duck typing" in the signature.

By the way, this Query method indeed returns DapperRow objects (a class implementing IDynamicMetaObjectProvider). One could think why they don't just return an anonymous type (then it's going to be accessed via dynamic anyway). One reason is performance, with an anonymous type the dynamic access would be using a Reflection (with a cache mechanism, so it works faster than normal Reflection, but it's Reflection anyway), a type implementing IDyamicMetaObjectProvider implements itmself the access and dispatch rules, so it should be faster. But there is another more fundamental reason, based on what you can read here

Anonymous objects are internal, which means their members are very restricted outside of the assembly that declares them. dynamic respects accessibility, so pretends not to be able to see those members.

Notice that when you use Reflection you can decide to ask access to non public items, but dynamic uses Reflection "as a good guy", trying to access only public items, so for this case where Dapper is in an assembly and your code in another, Anonymous Types + dynamic is not an option. On the contrary, for code living in the same assembly, like the one below, it's a good option.

static dynamic CreateObject()
{
 
 return new {Name="Francois"};
 
}
  
static void Main(string[] args)
{
 dynamic o1 = CreateObject();
 Console.WriteLine(o1.Name);
}

No comments:

Post a Comment