找出在expando对象上访问的属性

时间:2017-06-12 22:03:50

标签: c# dictionary dynamic expandoobject

我使用模板引擎从c#对象(嵌套)渲染模板。我想反思并找出每个模板字符串中使用的属性/对象。

理想的方法是建立一个"虚拟"表示正确形状的对象并在模板中呈现它。然后,我会检查此对象,以找出访问了哪些属性。这样我就可以保持这个逻辑与模板库无关。

知道如何实现这个吗? expando对象是动态构建的:

var dynamicObject = new ExpandoObject() as IDictionary<string, Object>;
foreach (var property in properties) {
    dynamicObject.Add(property.Key,property.Value);
}

这些方面有一些想法:

public class DummyObject {

    public DummyObject() {
        Accessed = new Dictionary<string, bool>();
    }
    public Dictionary<string, bool> Accessed;

    object MyProp {
        get {
            Accessed["MyProp"] = true;
            return "";
        }
    }
}

但是这个自定义属性显然不适用于dictionary / expando对象。有关前进路线的任何想法吗?

1 个答案:

答案 0 :(得分:1)

您可以覆盖TryGetMember上的DynamicObject方法:

public sealed class LoggedPropertyAccess : DynamicObject {
    public readonly HashSet<string> accessedPropertyNames = new HashSet<string>();
    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        accessedPropertyNames.Add(binder.Name);
        result = "";
        return true;
    }
}

然后以下将输出访问的属性名称

dynamic testObject = new LoggedPropertyAccess();
string firstname = testObject.FirstName;
string lastname = testObject.LastName;
foreach (var propertyName in testObject.accessedPropertyNames) {
    Console.WriteLine(propertyName);
}
Console.ReadKey();

N.B。这里仍然存在一个问题 - 只有模板库只需要来自属性的string时,这才有效。以下代码将失败,因为每个属性都将返回一个字符串:

DateTime dob = testObject.DOB;

为了解决这个问题,并允许嵌套对象,让TryGetMember返回LoggedPropertyAccess的新实例。然后,您也可以覆盖TryConvert方法;您可以根据转换为不同类型(完整代码)返回不同的值:

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

namespace DynamicObjectGetterOverride {
    public sealed class LoggedPropertyAccess : DynamicObject {
        public readonly Dictionary<string, object> __Properties = new Dictionary<string, object>();
        public readonly HashSet<string> __AccessedProperties = new HashSet<string>();

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            if (!__Properties.TryGetValue(binder.Name, out result)) {
                var ret = new LoggedPropertyAccess();
                __Properties[binder.Name] = ret;
                result = ret;
            }
            __AccessedProperties.Add(binder.Name);
            return true;
        }

        //this allows for setting values which aren't instances of LoggedPropertyAccess
        public override bool TrySetMember(SetMemberBinder binder, object value) {
            __Properties[binder.Name] = value;
            return true;
        }

        private static Dictionary<Type, Func<object>> typeActions = new Dictionary<Type, Func<object>>() {
            {typeof(string), () => "dummy string" },
            {typeof(int), () => 42 },
            {typeof(DateTime), () => DateTime.Today }
        };

        public override bool TryConvert(ConvertBinder binder, out object result) {
            if (typeActions.TryGetValue(binder.Type, out var action)) {
                result = action();
                return true;
            }
            return base.TryConvert(binder, out result);
        }
    }
}

并使用如下:

using System;
using static System.Console;

namespace DynamicObjectGetterOverride {
    class Program {
        static void Main(string[] args) {
            dynamic testObject = new LoggedPropertyAccess();
            DateTime dob = testObject.DOB;
            string firstname = testObject.FirstName;
            string lastname = testObject.LastName;

            dynamic address = testObject.Address;
            address.House = "123";
            address.Street = "AnyStreet";
            address.City = "Anytown";
            address.State = "ST";
            address.Country = "USA";

            WriteLine("----- Writes the returned values from reading the properties");
            WriteLine(new { firstname, lastname, dob });
            WriteLine();

            WriteLine("----- Writes the actual values of each property");
            foreach (var kvp in testObject.__Properties) {
                WriteLine($"{kvp.Key} = {kvp.Value}");
            }
            WriteLine();

            WriteLine("----- Writes the actual values of a nested object");
            foreach (var kvp in testObject.Address.__Properties) {
                WriteLine($"{kvp.Key} = {kvp.Value}");
            }
            WriteLine();

            WriteLine("----- Writes the names of the accessed properties");
            foreach (var propertyName in testObject.__AccessedProperties) {
                WriteLine(propertyName);
            }
            ReadKey();
        }
    }
}