다음을 통해 공유


Debugging Dynamic objects in C# Part 1

After a long time spent working on dev 10 features and fixing the may big and small things i have finally had the time to cobble togather a post.

What’s this post about ?

To begin with i will be talking about debugging dynamic objects.

With C# 4.0 we can instantiate and perform operations on objects  from dynamic languages like iron python, Iron ruby by using the dynamic keyword. In this post i will show you the tools added to  inspection these objects and how one can reuse this for debugging COM objects.

Introduction to Dynamic

Basically the dynamic keyword can be used to define the type of a local, field, return type or parameter. By doing so the user allows the compiler to defers the binding of calls and member accesses to run-time. In order to do this the compiler does some code generation that may or may be interesting to you ( i will briefly touch on this later), but what is sure to pop to ones mind is “ What’s this good for ? ”.

The primary use of this to bridge the gap between the .Net type system and its set of strongly typed languages and other alternate type systems that are in use by the developer to get his/her job one ( think COM, Scripting runtimes , DOM etc). The compiler does this by packaging all the information about the call in a payload and generates a call to the DLR  (dynamic language runtime). This call is executed at runtime where the DLR takes charge of routing the call to the correct provider (IronPython, COM, IronRuby) of the object. This provider  then tries to bind the call and execute it.

What’s the Problem with Debugging this ?

Consider the following code

 using System;
using Microsoft.Scripting;//requires reference to Microsoft.Scripting.dll and IronPython.dll
using IronPython.Compiler;
using IronPython.Hosting;

public class C
{
    static void Main(string[] args)
    {
        var sr = Python.CreateEngine();
        // similar overload exists for an file instead of a inline string
        var code = sr.CreateScriptSourceFromString(@"   
class x(object):
 class_val = 5
 def some_method(self): pass
 def meth1(self, foo): 
   return foo
a = x()
a.val = 3
a.Name = 'Sree'", SourceCodeKind.Statements);
        var scope = sr.CreateScope();
        code.Execute(scope);
        dynamic y = scope.GetVariable("a");
        System.Diagnostics.Debugger.Break();

    }
}
  

Try inspecting y,

image

since we are talking about dynamic objects by nature they don’t have a strong type that representaion the structure of the object. In this case i am using Iron python, which happens to represent the members in a hash table.

In the case of COM the structure of the object is actually in an alternate type system.

 dynamic XlApplication = new Microsoft.Office.Interop.Excel.Application();
dynamic thisWorkbook = XlApplication.Workbooks.Add();
dynamic xlSheet = thisWorkbook.Worksheets.Add(After: thisWorkbook.ActiveSheet);
System.Diagnostics.Debugger.Break();

Try inspecting xlSheet,

image

In either case the basic debugging tools in VS fail as they have no way of discovering this and only end showing the most basic information, which in almost every case is useless.

In Conclusion

  1. Dynamic languages such as IronRuby or IronPython do not have strong types that represent the members they contain
  2. COM support is limited to Interop assembly generated and quickly one can fall of the cliff with no type information.
  3. The IntelliSense® tool does not show any of the dynamic members ( since it’s driven by the static type system).

debugging can be hampered by an inability to query the state of the object while stepping though code.

So what does the solution look like ?

Dynamic View

In addition to the static view, the C# expression evaluator will add a special node for 

  1. Objects implementing IDynamicObject
  2. COM objects with out a PIA or strong type (Primary interop Assembly)

named “Dynamic View”. This is similar to the “Results View” node that was added for LINQ debugging, where the user could see the results of the lazy evaluated queries of linq. 

The “Dynamic View” node has the following behavior.

  1. Expanding the Dynamic View node will query the IDynamicObject object for the members available for display and get the value for each.

image

  1. The children of the Dynamic View are immutable.
  2. Choosing “Add to Watch” for any child of the Dynamic View inserts a new watch that casts the object to dynamic (i.e. ((dynamic)y).Name).
  3. The Dynamic View and its children are assumed to be side-effecting, and therefore stepping in the debugger will not automatically re-evaluate the Dynamic View or its children; they are made stale.
  4. The Dynamic View and/or its children will need to be manually refreshed by the user.

A “dynamic” format specifier has been added to the expression evaluator. This allows the Dynamic View to be added to the watch by suffixing expressions with “, dynamic”

image

Pdb support

In response to this implementation, a PDB payload has been added for local variables typed in some way as dynamic. This payload is necessary since there is no metadata corresponding to locals and since there no actual type called dynamic anywhere in the CLR. The compiler must find some way to decorate the locals to infer that its a dynamic local at runtime. The payload includes the name of the local, its slot ID and a boolean array (length<=128) that represents a pre-order traversal of the type. For example, consider:

Dictionary<int, dynamic> dict = new Dictionary<int, dynamic>();

This code will produce a boolean array of false,false,true to indicate the portion of the type that is dynamic.

So remember if you want to debug dynamic locals be sure you generate the debug information(pdb) and have it where you can access it

With regard to declaration (as opposed to inspection), in some embodiments, declaration of dynamic variables within an Immediate window is supported, including complex generic types (e.g. Dictionary<int, List<dynamic>>). But more on this later …

More later … cheers

kick it on DotNetKicks.com

Comments