Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   The dynamic Primitive Type

C# is a type-safe programming language. This means that all expressions resolve into an instance of a type and the compiler will generate only code that is attempting to perform an operation that is valid for this type. The benefit of a type-safe programming language over a non–type-safe programming language is that many programmer errors are detected at compile time, helping to ensure that the code is correct before you attempt to execute it. In addition, compile-time languages can typically produce smaller and faster code because they make more assumptions at compile time and bake those assumptions into the resulting IL and metadata.

However, there are also many occasions when a program has to act on information that it doesn’t know about until it is running. Although you can use type-safe programming languages (like C#) to interact with this information, the syntax tends to be clumsy, especially because you tend to work a lot with strings, and performance is hampered as well. If you are writing a pure C# application, then the only occasion you have for working with runtime-determined information is when you are using reflection (discussed in Chapter 23). However, many developers also use C# to communicate with components that are not implemented in C#. Some of these components could be .NET-dynamic languages such as Python or Ruby, or COM objects that support the IDispatch interface (possibly implemented in native C or C++), or HTML Document Object Model (DOM) objects (implemented using various languages and technologies). Communicating with HTML DOM objects is particularly useful when building a Microsoft Silverlight application.

To make it easier for developers using reflection or communicating with other components, the C# compiler offers you a way to mark an expression’s type as dynamic. You can also put the result of an expression into a variable and you can mark a variable’s type as dynamic. This dynamic expression/ variable can then be used to invoke a member such as a field, a property/indexer, a method, delegate, and unary/binary/conversion operators. When your code invokes a member by using a dynamic expression/variable, the compiler generates special IL code that describes the desired operation. This special code is referred to as the payload. At run time, the payload code determines the exact opera- tion to execute based on the actual type of the object now referenced by the dynamic expression/ variable.

Here is some code to demonstrate what I’m talking about.

 

internal static class DynamicDemo { public static void Main() {

dynamic value;

for (Int32 demo = 0; demo < 2; demo++) {

value = (demo == 0) ? (dynamic) 5 : (dynamic) "A"; value = value + value;

M(value);

}

}

 

private static void M(Int32 n) { Console.WriteLine("M(Int32): " + n); } private static void M(String s) { Console.WriteLine("M(String): " + s); }

}


When I execute Main, I get the following output.



 

M(Int32): 10

M(String): AA

 

To understand what’s happening, let’s start by looking at the + operator. This operator has oper- ands of the dynamic type. Because value is dynamic, the C# compiler emits payload code that will examine the actual type of value at run time and determine what the + operator should actually do.

The first time the + operator evaluates, value contains 5 (an Int32) and the result is 10 (also an Int32). This puts this result in the value variable. Then, the M method is called, passing it value. For the call to M, the compiler will emit payload code that will, at run time, examine the actual type of the argument being passed to M and determine which overload of the M method to call. When value contains an Int32, the overload of M that takes an Int32 parameter is called.

The second time the + operator evaluates, value contains “A” (a String) and the result is “AA” (the result of concatenating “A” with itself). Then, the M method is called again, passing it value. This time, the payload code determines that the actual type being passed to M is a String and calls the over- load of M that takes a String parameter.

When the type of a field, method parameter, or method return type is specified as dynamic, the compiler converts this type to the System.Object type and applies an instance of System.Run­ time.CompilerServices.DynamicAttribute to the field, parameter, or return type in metadata. If a local variable is specified as dynamic, then the variable’s type will also be of type Object, but the DynamicAttribute is not applied to the local variable because its usage is self-contained within the method. Because dynamic is really the same as Object, you cannot write methods whose signature differs only by dynamic and Object.

It is also possible to use dynamic when specifying generic type arguments to a generic class (refer- ence type), a structure (value type), an interface, a delegate, or a method. When you do this, the com- piler converts dynamic to Object and applies DynamicAttribute to the various pieces of metadata where it makes sense. Note that the generic code that you are using has already been compiled and will consider the type to be Object; no dynamic dispatch will be performed because the compiler did not produce any payload code in the generic code.

Any expression can implicitly be cast to dynamic because all expressions result in a type that is derived from Object.2 Normally, the compiler does not allow you to write code that implicitly casts an expression from Object to another type; you must use explicit cast syntax. However, the compiler does allow you to cast an expression from dynamic to another type by using implicit cast syntax.

 

Object o1 = 123; // OK: Implicit cast from Int32 to Object (boxing) Int32 n1 = o; // Error: No implicit cast from Object to Int32 Int32 n2 = (Int32) o; // OK: Explicit cast from Object to Int32 (unboxing)

 

dynamic d1 = 123; // OK: Implicit cast from Int32 to dynamic (boxing) Int32 n3 = d1; // OK: Implicit cast from dynamic to Int32 (unboxing)

 

 
 

2 And, as always, value types will be boxed.


Although the compiler allows you to omit the explicit cast when casting from dynamic to some other type, the CLR will validate the cast at run time to ensure that type safety is maintained. If the ob- ject’s type is not compatible with the cast, the CLR will throw an InvalidCastException exception.

Note that the result of evaluating a dynamic expression is a dynamic expression. Examine this code.

 

dynamic d = 123;

var result = M(d); // Note: 'var result' is the same as 'dynamic result'

 

Here, the compiler allows the code to compile because it doesn’t know at compile time which M method it will call. Therefore, it also does not know what type of result M will return. And so, the compiler assumes that the result variable is of type dynamic itself. You can verify this by placing your mouse over var in the Visual Studio editor; the IntelliSense window will indicate 'dynamic:

Represents an object whose operations will be resolved at runtime.' If the M method invoked at run time has a return type of void, a Microsoft.CSharp.RuntimeBinder.Runtime­ BinderException exception is thrown.

       
   
 
 

 

However, when converting from dynamic to another static type, the result’s type is, of course, the static type. Similarly, when constructing a type by passing one or more dynamic arguments to its constructor, the result is the type of object you are constructing.

 

dynamic d = 123;

var x = (Int32) d; // Conversion: 'var x' is the same as 'Int32 x'

var dt = new DateTime(d); // Construction: 'var dt' is the same as 'DateTime dt'

 

If a dynamic expression is specified as the collection in a foreach statement or as a resource in a using statement, the compiler will generate code that attempts to cast the expression to the non-generic System.IEnumerable interface or to the System.IDisposable interface, respec- tively. If the cast succeeds, the expression is used and the code runs just fine. If the cast fails, a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException exception is thrown.


 

Here is an example of some C# code that uses COM IDispatch to create a Microsoft Excel work- book and places a string in cell A1.

 

using Microsoft.Office.Interop.Excel;

...

public static void Main() {

Application excel = new Application(); excel.Visible = true; excel.Workbooks.Add(Type.Missing);

((Range)excel.Cells[1, 1]).Value = "Text in cell A1"; // Put this string in cell A1

}

 

Without the dynamic type, the value returned from excel.Cells[1, 1] is of type Object, which must be cast to the Range type before its Value property can be accessed. However, when produc- ing a runtime callable wrapper assembly for a COM object, any use of VARIANT in the COM method is really converted to dynamic; this is called dynamification. Therefore, because excel.Cells[1, 1] is of type dynamic, you do not have to explicitly cast it to the Range type before its Value property can be accessed. Dynamification can greatly simplify code that interoperates with COM objects. Here is the simpler code.

 

using Microsoft.Office.Interop.Excel;

...

public static void Main() {

Application excel = new Application(); excel.Visible = true; excel.Workbooks.Add(Type.Missing);

excel.Cells[1, 1].Value = "Text in cell A1"; // Put this string in cell A1

}


The following code shows how to use reflection to call a method (“Contains”) on a String target (“Jeffrey Richter”) passing it a String argument (“ff”) and storing the Boolean result in a local vari- able (result).

 

Object target = "Jeffrey Richter"; Object arg = "ff";

 

// Find a method on the target that matches the desired argument types Type[] argTypes = new Type[] { arg.GetType() };

MethodInfo method = target.GetType().GetMethod("Contains", argTypes);

 

// Invoke the method on the target passing the desired arguments Object[] arguments = new Object[] { arg };

Boolean result = Convert.ToBoolean(method.Invoke(target, arguments));

 

Using C#’s dynamic type, this code can be rewritten with greatly improved syntax.

 

dynamic target = "Jeffrey Richter"; dynamic arg = "ff";

Boolean result = target.Contains(arg);

 

Earlier, I mentioned that the C# compiler emits payload code that, at run time, figures out what operation to perform based on the actual type of an object. This payload code uses a class known as a runtime binder. Different programming languages define their own runtime binders that encapsulate the rules of that language. The code for the C# runtime binder is in the Microsoft.CSharp.dll as- sembly, and you must reference this assembly when you build projects that use the dynamic keyword. This assembly is referenced in the compiler’s default response file, CSC.rsp. It is the code in this assem- bly that knows to produce code (at run time) that performs addition when the + operator is applied to two Int32 objects and concatenation when applied to two String objects.

At run time, the Microsoft.CSharp.dll assembly will have to load into the AppDomain, which hurts your application’s performance and increases memory consumption. Microsoft.CSharp.dll also loads System.dll and System.Core.dll. If you are using dynamic to help you interoper- ate with COM components, then System.Dynamic.dll will also load. And when the payload code executes, it generates dynamic code at run time; this code will be in an in-memory assembly called “Anonymously Hosted DynamicMethods Assembly.” The purpose of this code is to improve the

performance of dynamic dispatch in scenarios where a particular call site is making many invocations using dynamic arguments that have the same runtime type.

Due to all the overhead associated with C#’s built-in dynamic evaluation feature, you should consciously decide that you are getting sufficient syntax simplification from the dynamic feature to make it worth the extra performance hit of loading all these assemblies and the extra memory that they consume. If you have only a couple places in your program where you need dynamic behavior, it might be more efficient to just do it the old-fashioned way, by calling reflection methods (for man- aged objects) or with manual casting (for COM objects).

At run time, the C# runtime binder resolves a dynamic operation according to the runtime type of the object. The binder first checks to see if the type implements the IDynamicMetaObjectProvider interface. If the object does implement this interface, then the interface’s GetMetaObject method is


called, which returns a DynamicMetaObject-derived type. This type can process all of the member, method, and operator bindings for the object. Both the IDynamicMetaObjectProvider interface and the DynamicMetaObject base class are defined in the System.Dynamic namespace, and both are in the System.Core.dll assembly.

Dynamic languages, such as Python and Ruby, endow their types with DynamicMetaObject- derived types so that they can be accessed in a way appropriate for them when manipulated from other programming languages (like C#). Similarly, when accessing a COM component, the C# runtime binder will use a DynamicMetaObject-derived type that knows how to communicate with a COM component. The COM DynamicMetaObject-derived type is defined in the System.Dynamic.dll assembly.

If the type of the object being used in the dynamic expression does not implement the IDynamic­ MetaObjectProvider interface, then the C# compiler treats the object like an instance of an ordinary C#-defined type and performs operations on the object using reflection.

One of the limitations of dynamic is that you can only use it to access an object’s instance mem- bers because the dynamic variable must refer to an object. But, there are occasions when it would be useful to dynamically invoke static members of a type where the type is determined at run time. To accomplish this, I have created a StaticMemberDynamicWrapper class that derives from Sys­ tem.Dynamic.DynamicObject, which implements the IDynamicMetaObjectProvider interface. The class internally uses quite a bit of reflection (covered in Chapter 23, “Assembly Loading and Reflection”). Here is the code for my StaticMemberDynamicWrapper class.

 

internal sealed class StaticMemberDynamicWrapper : DynamicObject { private readonly TypeInfo m_type;

public StaticMemberDynamicWrapper(Type type) { m_type = type.GetTypeInfo(); }

 

public override IEnumerable<String> GetDynamicMemberNames() { return m_type.DeclaredMembers.Select(mi => mi.Name);

}

 

public override Boolean TryGetMember(GetMemberBinder binder, out object result) { result = null;

var field = FindField(binder.Name);

if (field != null) { result = field.GetValue(null); return true; }

 

var prop = FindProperty(binder.Name, true);

if (prop != null) { result = prop.GetValue(null, null); return true; } return false;

}

 

public override Boolean TrySetMember(SetMemberBinder binder, object value) { var field = FindField(binder.Name);

if (field != null) { field.SetValue(null, value); return true; }

 

var prop = FindProperty(binder.Name, false);

if (prop != null) { prop.SetValue(null, value, null); return true; } return false;

}


public override Boolean TryInvokeMember(InvokeMemberBinder binder, Object[] args, out Object result) {

MethodInfo method = FindMethod(binder.Name);

if (method == null) { result = null; return false; } result = method.Invoke(null, args);

return true;

}

 

private MethodInfo FindMethod(String name, Type[] paramTypes) {

return m_type.DeclaredMethods.FirstOrDefault(mi => mi.IsPublic && mi.IsStatic && mi.Name == name

&& ParametersMatch(mi.GetParameters(), paramTypes));

}

 

private Boolean ParametersMatch(ParameterInfo[] parameters, Type[] paramTypes) { if (parameters.Length != paramTypes.Length) return false;

for (Int32 i = 0; i < parameters.Length; i++)

if (parameters[i].ParameterType != paramTypes[i]) return false; return true;

}

 

private FieldInfo FindField(String name) {

return m_type.DeclaredFields.FirstOrDefault(fi => fi.IsPublic && fi.IsStatic && fi.Name == name);

}

 

private PropertyInfo FindProperty(String name, Boolean get) { if (get)

return m_type.DeclaredProperties.FirstOrDefault(

pi => pi.Name == name && pi.GetMethod != null && pi.GetMethod.IsPublic && pi.GetMethod.IsStatic);

 

return m_type.DeclaredProperties.FirstOrDefault(

pi => pi.Name == name && pi.SetMethod != null && pi.SetMethod.IsPublic && pi.SetMethod.IsStatic);

}

}

 

To invoke a static member dynamically, construct an instance of this class by passing in the Type you want it to operate on and put the reference in a dynamic variable. Then, invoke the desired static member by using instance member syntax. Here is an example of how to invoke String’s static Concat(String, String) method.

 

dynamic stringType = new StaticMemberDynamicWrapper(typeof(String));

var r = stringType.Concat("A", "B"); // dynamically invoke String’s static Concat method Console.WriteLine(r); // Displays "AB"


C HA P T E R 6


Date: 2016-03-03; view: 784


<== previous page | next page ==>
Nbsp;   Object Hash Codes | Nbsp;   The Different Kinds of Type Members
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.013 sec.)