-
Notifications
You must be signed in to change notification settings - Fork 86
symplsetmemberbinder and binding .net instance members
At runtime, when trying to set a member of a .NET static object, the default .NET meta-object will call FallbackSetMember on SymplSetMemberBinder. There is more code to setting members than getting them, and by convention, the resulting DynamicMetaObject's expression needs to ensure it returns the value stored. As a reminder, if the object that flows into the CallSite is some dynamic object, then it's DynamicMetaObject's BindSetMember will produce a rule for setting the member.
Here's the code for SymplSetMemberBinder's FallbackSetMember from runtime.cs, which is essentially all there is to the class:
public override DynamicMetaObject FallbackSetMember(
DynamicMetaObject targetMO, DynamicMetaObject value,
DynamicMetaObject errorSuggestion) {
// ... Deleted checking for COM and need to Defer for now ...
var flags = BindingFlags.IgnoreCase | BindingFlags.Static |
BindingFlags.Instance | BindingFlags.Public;
var members = targetMO.LimitType.GetMember(this.Name, flags);
if (members.Length == 1) {
MemberInfo mem = members[0];
Expression val;
if (mem.MemberType == MemberTypes.Property)
val = Expression.Convert(
value.Expression,
((PropertyInfo)mem).PropertyType);
else if (mem.MemberType == MemberTypes.Field)
val = Expression.Convert(value.Expression,
((FieldInfo)mem).FieldType);
else
return (errorSuggestion ??
RuntimeHelpers.CreateThrow(
targetMO, null,
BindingRestrictions.GetTypeRestriction(
targetMO.Expression,
targetMO.LimitType),
typeof(InvalidOperationException),
"Sympl only supports setting Properties " +
"and fields at this time."));
return new DynamicMetaObject(
RuntimeHelpers.EnsureObjectResult(
Expression.Assign(
Expression.MakeMemberAccess(
Expression.Convert(targetMO.Expression,
members[0].DeclaringType),
members[0]),
val)),
BindingRestrictions.GetTypeRestriction(
targetMO.Expression,
targetMO.LimitType));
} else {
return errorSuggestion ??
RuntimeHelpers.CreateThrow(
targetMO, null,
BindingRestrictions.GetTypeRestriction(
targetMO.Expression,
targetMO.LimitType),
typeof(MissingMemberException),
"IDynObj member name conflict.");
Let's first talk about what we aren't talking about now. This code snippet omits the code to check if the target is a COM object and to use built-in COM support. See section for information adding this to your binders. The snippet also omits some very important code that protects binders and DynamicMetaObjects from infinitely looping due to producing bad rules. It is best to discuss this in one place, so see section for how the infinite loop happens and how to prevent it for all binders.
FallbackSetMember uses .NET reflection to get the member with the name in the binder's metadata. If there's exactly one, the binder needs to confirm the kind of member for two reasons. The first is to make sure the kind of member is supported for setting in Sympl. The second is due to .NET's reflection API not having a single name for getting the type of values the member can store. FallbackSetMember creates a ConvertExpression to convert the value to the member's type. To be more correct or consistent, the code should check for the property or field being of type Type and the value being a TypeModel, similar to what ConvertArguments does. In this case, it could build an expression like the helper method GetRuntimeTypeMoFromModel does.
The FallbackSetMember returns a DynamicMetaObject result with an Assign node. The left hand side argument is the same expression created in SymplGetMemberBinder, so see that description for uses of DeclaringType and LimitType in the expression and restrictions. The right hand side is the ConvertExpression discuss above. Expression Tree Assign nodes guarantee returning the value stored, so the binder complies with that convention. The operation implementation expression passes through EnsureObjectResult in case it needs to be wrapped to ensure it is strictly typed as assignable to object. For more information, see section 3.2.4.
For restrictions, this binder doesn't need the binding helpers since it only has to test the target object. Also, since the code doesn't make any decisions based on the property or field's type, having restrictions consistent with the conversions isn't necessary here. If the code were conditional on whether the property or field was assignable, then the result would need more restrictions. You might think we need a restriction for the name to prevent the rule from firing for any name. However, this rule is only good on CallSites that point to this binder, and it only returns rules for this one name.
If there isn't exactly one member, or if the member is not a property or field, then Sympl either uses the suggested result or creates a DynamicMetaObject result that throws an Exception. See section for a discussion of CreateThrow and restrictions. See section 12 for a discussion of errorSuggestion arguments to binders.
Frontmatter
1 Introduction
1.1 Sources
1.2 Walkthrough Organization
2 Quick Language Overview
3 Walkthrough of Hello World
3.1 Quick Code Overview
3.2 Hosting, Globals, and .NET Namespaces Access
3.2.1 DLR Dynamic Binding and Interoperability -- a Very Quick Description
3.2.2 DynamicObjectHelpers
3.2.3 TypeModels and TypeModelMetaObjects
3.2.4 TypeModelMetaObject's BindInvokeMember -- Finding a Binding
3.2.5 TypeModelMetaObject.BindInvokeMember -- Restrictions and Conversions
3.3 Import Code Generation and File Module Scopes
3.4 Function Call and Dotted Expression Code Generation
3.4.1 Analyzing Function and Member Invocations
3.4.2 Analyzing Dotted Expressions
3.4.3 What Hello World Needs
3.5 Identifier and File Globals Code Generation
3.6 Sympl.ExecuteFile and Finally Running Code
4 Assignment to Globals and Locals
5 Function Definition and Dynamic Invocations
5.1 Defining Functions
5.2 SymplInvokeBinder and Binding Function Calls
6 CreateThrow Runtime Binding Helper
7 A Few Easy, Direct Translations to Expression Trees
7.1 Let* Binding
7.2 Lambda Expressions and Closures
7.3 Conditional (IF) Expressions
7.4 Eq Expressions
7.5 Loop Expressions
8 Literal Expressions
8.1 Integers and Strings
8.2 Keyword Constants
8.3 Quoted Lists and Symbols
8.3.1 AnalyzeQuoteExpr -- Code Generation
8.3.2 Cons and List Keyword Forms and Runtime Support
9 Importing Sympl Libraries and Accessing and Invoking Their Globals
10 Type instantiation
10.1 New Keyword Form Code Generation
10.2 Binding CreateInstance Operations in TypeModelMetaObject
10.3 Binding CreateInstance Operations in FallbackCreateInstance
10.4 Instantiating Arrays and GetRuntimeTypeMoFromModel
11 SymplGetMemberBinder and Binding .NET Instance Members
12 ErrorSuggestion Arguments to Binder FallbackX Methods
13 SymplSetMemberBinder and Binding .NET Instance Members
14 SymplInvokeMemberBinder and Binding .NET Member Invocations
14.1 FallbackInvokeMember
14.2 FallbackInvoke
15 Indexing Expressions: GetIndex and SetIndex
15.1 SymplGetIndexBinder's FallbackGetIndex
15.2 GetIndexingExpression
15.3 SymplSetIndexBinder's FallbackSetIndex
16 Generic Type Instantiation
17 Arithmetic, Comparison, and Boolean Operators
17.1 Analysis and Code Generation for Binary Operations
17.2 Analysis and Code Generation for Unary Operations
17.3 SymplBinaryOperationBinder
17.4 SymplUnaryOperationBinder
18 Canonical Binders or L2 Cache Sharing
19 Binding COM Objects
20 Using Defer When MetaObjects Have No Value
21 SymPL Language Description
21.1 High-level
21.2 Lexical Aspects
21.3 Built-in Types
21.4 Control Flow
21.4.1 Function Call
21.4.2 Conditionals
21.4.3 Loops
21.4.4 Try/Catch/Finally and Throw
21.5 Built-in Operations
21.6 Globals, Scopes, and Import
21.6.1 File Scopes and Import
21.6.2 Lexical Scoping
21.6.3 Closures
21.7 Why No Classes
21.8 Keywords
21.9 Example Code (mostly from test.sympl)
22 Runtime and Hosting
22.1 Class Summary
23 Appendixes
23.1 Supporting the DLR Hosting APIs
23.1.1 Main and Example Host Consumer
23.1.2 Runtime.cs Changes
23.1.3 Sympl.cs Changes
23.1.4 Why Not Show Using ScriptRuntime.Globals Namespace Reflection
23.1.5 The New DlrHosting.cs File
23.2 Using the Codeplex.com DefaultBinder for rich .NET interop
23.3 Using Codeplex.com Namespace/Type Trackers instead of ExpandoObjects
23.4 Using Codeplex.com GeneratorFunctionExpression
Other documents:
Dynamic Language Runtime
DLR Hostirng Spec
Expression Trees v2 Spec
Getting Started with the DLR as a Library Author
Sites, Binders, and Dynamic Object Interop Spec