C# - DynamicObject with Dynamic and create wrapper with DynamicObject

本文介绍如何利用C#中的DynamicObject创建动态属性对象,并演示了如何通过DynamicObject包装XElement,简化XML节点操作。

There is a expando object which allow you add/remove properties at runtime, DynamicObject provides ou with more capability and it is better used in places where a wrapper excels a raw XML file or script object syntax. 


First, we will intoduce the  DynamicObject, as stated in the References Section on "DynamicObject Class",which defines some methods such as TryGetMember, TrySetMember, TryInvokeMembers which acts as a proxy to the real value contained in the DynamicObject derived classes. Also, DynamicObject has special support from the DLR (Dynamic Language Runtime) which means it has some special translation when you use DynamicObject with "dynamic" keyword. 

 

First, we will see a example object with DynamicObject which has the ability to let you query/set values in case-insensitive fashion, which the real data is backed by a Dictionary. 

 

 

using System.Collections.Generic;
using System.Dynamic;

namespace DynamicObjectDemo
{
    class DynamicDictionary : DynamicObject
    {
        private Dictionary<string, object> dictionary = new Dictionary<string, object>();


        public int Count
        {
            get { return dictionary.Count; }
        }


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

            string name = binder.Name.ToLower();
            return dictionary.TryGetValue(name, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            dictionary[binder.Name.ToLower()] = value;

            return true;
        }
    }
}

 

 

and here is a test program to show you how to use the DynamicDictionary class. 

 

 

namespace DynamicObjectDemo
{
    /// <summary>
    /// Main method to test the DynamicObject
    /// </summary>
    /// <remarks>
    /// <para>
    /// Please find references information from: http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.aspx 
    /// </para>
    /// </remarks>
    class Program
    {
        static void Main(string[] args)
        {

            // you must use the "Dynamic" keyword to create object, otherwise, you won't be able to use get the dynamic behavior

            dynamic person = new DynamicDictionary();


            // adding new Dictionary Properties
            person.FirstName = "Ellen";  //  the dynamic language runtime (DLR) first uses the language binder to look for a static definition of a property in the class. If there is no such property, the DLR calls the TrySetMember method.
            person.LastName = "Adams";

            //getting values of the dynamic properties
            // the tryGetMember methods is called
            Console.WriteLine(person.FirstName + " " + person.lastname); // this time, we uses the lower case member

            Console.WriteLine("NUmber of dynamic properties" + person.Count);


            try
            {
                // the following will throw Exception at runtime, 
                // when the TryGetMember method return a false, and this causes a 
                // RuntimeBinderException
                Console.WriteLine(person.address);
            }
            catch (RuntimeBinderException ex)
            {
                Debug.WriteLine("caught exception calling person.address: {0}", ex.ToString());
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Some unknown exception : {0}", ex.ToString());
            }
        }
    }
}

 

 

With DynamicObject, you can do even more, suppose that we have an XElement and we know we can create an XElement with some syntax as below. 

 

XElement contactXML =
    new XElement("Contact",
    new XElement("Name", "Patrick Hines"),
    new XElement("Phone", "206-555-0144"),
    new XElement("Address",
        new XElement("Street1", "123 Main St"),
        new XElement("City", "Mercer Island"),
        new XElement("State", "WA"),
        new XElement("Postal", "68042")
    )
);

 

 

however, we can make it even simpler. what we have in mind is something like this: 

 

dynamic contact = new DynamicXMLNode("Contacts");
contact.Name = "Patrick Hines";
contact.Phone = "206-555-0144";
contact.Address = new DynamicXMLNode();
contact.Address.Street = "123 Main St";
contact.Address.City = "Mercer Island";
contact.Address.State = "WA";
contact.Address.Postal = "68402";

 

 

we might need to write Dyanmic Extended class  with the following override.s 

 

TryGetMember : the Contact.Address.Street part in the Contact.Address.Street statement (the part to get some member back) 

TrySetMember: contact.Name = "Patrick Hines";

TryConvert: contact.Name = "Patrick Hines"; (Expected value type is String, but returned type is a DynamicXMLNode)

TryInvokeMember: we have hide the XElement , this enables us to the hidden methods supported by the Xlement

 

here is the full code of the implementation of DynamicXMLNode.

 

using System;
using System.Dynamic;
using System.Reflection;
using System.Xml.Linq;

namespace DynamicObjectDemo
{
    /// <summary>
    /// A wrapper on the XElement
    /// </summary>
    /// <remarks>
    /// <para>
    /// You can find some reference page from 
    ///     * Dynamic in C# 4.0: Creating Wrappers with DynamicObject:  http://blogs.msdn.com/b/csharpfaq/archive/2009/10/19/dynamic-in-c-4-0-creating-wrappers-with-dynamicobject.aspx 
    /// </para>
    /// </remarks>
    public class DynamicXMLNode : DynamicObject
    {
        XElement node;

        public DynamicXMLNode(XElement node)
        {
            this.node = node;
        }

        public DynamicXMLNode()
        {
            
        }

        public DynamicXMLNode(string name)
        {
            node =new XElement(name);
        }


        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            XElement getNode = node.Element(binder.Name);
            if (getNode != null)
            {
                result = new DynamicXMLNode(getNode);
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            XElement setNode = node.Element(binder.Name);
            if (setNode != null)
            {
                setNode.SetValue(binder.Name);
            }
            else
            {
                if (value.GetType() == typeof (DynamicXMLNode))
                {
                    node.Add(new XElement(binder.Name));
                }
                else
                {
                    node.Add(new XElement(binder.Name, value));
                }
            }
            return true;
        }

        // TryGetMember always returns an instance of DynamicXMLNode. How do I get the actual value of the XML node? For example, I want the following line to work, but now it throws an exception.
        //   String state = contact.Address.State
        // one option is to return the actual values for leaf nodes, but to explore another option: you can try the type conversion, just add the following method to the DynaxmicXMLNode class
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.Type == typeof(string))
            {
                result = node.Value;
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

        // though we can get manipulate indirectly to XElement values wrapped by the DynamicXMLNode, we can even get the contained result 
        // out of the DynamicXMLNode, we cannot call methods which is suppose to work on XElement, here is what in addition you can 
        // to get Access to XElement methods
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            Type xmlType = typeof(XElement);
            try
            {
                result = xmlType.InvokeMember(binder.Name,
                                              BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
                                              null,
                                              node,
                                              args);
                return true;
            }
            catch
            {
                result = null;
                return false;
            }
        }
    }
}

 

to give an impression on the capability that the DynamixXMLNode has empowered us, let 'see the test code below. 

    public class DynamicXMLNodeMain
    {
        public static void DynamicXMLNodeCreateReturnValidDynamixXMLNode()
        {
            dynamic contact = CreateDynamicXMLNode();
            if (contact != null)
            {
                Console.WriteLine("Created DynamicXMLNode ");
            }
            else
            {
                Console.WriteLine("Failed to create DynamicXMLNode");
            }
        }

        public  static void DynamicXMLNodeConvertToStringReturnXElementValue()
        {
            dynamic contact = CreateDynamicXMLNode();
            string state = contact.Address.State;
            Console.WriteLine("State is {0}", state);
        }

        public static void DynamicXMLNodeTryInvokeMemberCallXElementMethods()
        {
            dynamic contact = CreateDynamicXMLNode();
            contact.Address.Postal.SetValue("newPostValue");
            string newPostal = contact.Address.Postal;
            if (newPostal == "newPostValue")
            {
                Console.WriteLine("Set new Post value");
            }
            else
            {
                Console.WriteLine("Failed to set new postal value");
            }
        }

        public static DynamicXMLNode CreateDynamicXMLNode()
        {
            dynamic contact = new DynamicXMLNode("Contacts");
            contact.Name = "Patrick Hines";
            contact.Phone = "206-555-0144";
            contact.Address = new DynamicXMLNode();
            contact.Address.Street = "123 Main St.";
            contact.Address.City = "Mercer Island";
            contact.Address.State = "NA";
            contact.Address.Postal = "68402";
            return contact;
        }
    }

 

and this is the Main method which invoke them.. 

 

        static void Main(string[] args)
        {
            // This is test on the DynamicXMLNode
            DynamicXMLNodeMain.DynamicXMLNodeCreateReturnValidDynamixXMLNode();
            DynamicXMLNodeMain.DynamicXMLNodeConvertToStringReturnXElementValue();
            DynamicXMLNodeMain.DynamicXMLNodeTryInvokeMemberCallXElementMethods();

        }

 

 

References

 

[1] DynamicObject Class:  

[2] Dynamic in C# 4.0: Creating Wrappers with DynamicObject

 

### C# 中 `DynamicObject` 的赋值 在 C# 中,`DynamicObject` 是一种特殊的类,允许创建具有动态属性的对象。为了给 `DynamicObject` 赋值,通常会继承 `System.Dynamic.DynamicObject` 并重写其方法以定义行为。 #### 创建和初始化 `DynamicObject` 可以通过继承 `DynamicObject` 来创建自定义的动态对象,并通过设置字段或属性来进行赋值: ```csharp using System; using System.Dynamic; public class MyDynamic : DynamicObject { private Dictionary<string, object> _properties = new Dictionary<string, object>(); public override bool TrySetMember(SetMemberBinder binder, object value) { // 将成员名称作为键存入字典中 _properties[binder.Name] = value; return true; } public override bool TryGetMember(GetMemberBinder binder, out object result) { // 如果存在则返回对应的值;否则返回 null return _properties.TryGetValue(binder.Name, out result); } } // 使用示例 MyDynamic obj = new MyDynamic(); obj.PropertyName = "PropertyValue"; // 动态添加属性并赋值 Console.WriteLine(obj.PropertyName); // 输出 PropertyValue ``` 此代码展示了如何利用 `TrySetMember` 方法拦截对不存在成员的赋值操作[^1]。 对于更简单的场景,如果不需要完全控制动态行为,则可以直接使用内置的 `ExpandoObject` 类,它提供了更为简便的方式来管理动态属性: ```csharp dynamic expandoObj = new ExpandoObject(); expandoObj.NewProperty = "Some Value"; Console.WriteLine(expandoObj.NewProperty); // Some Value ``` 上述例子中的 `ExpandoObject` 可以像常规对象一样轻松地为其新增任意数量的新属性,并且这些新属性可以在后续的操作中正常读取和修改[^2]。 当涉及到复杂的数据结构或者需要更加灵活的行为时,建议采用扩展 `DynamicObject` 的方式来自定义逻辑。而对于简单的需求来说,`ExpandoObject` 提供了一个快速而有效的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值