找出在 expando 对象上访问了哪些属性

Figure out which properties accessed on expando object

我使用一个模板引擎来呈现来自 c# 对象的模板(嵌套)。我想反思并弄清楚每个模板字符串中使用了哪些属性/对象。

理想的方法是构建一个代表正确形状的 "dummy" 对象并将其呈现在模板中。之后我会检查这个对象以找出访问了哪些属性。这将允许我保持这个逻辑独立于模板库。

知道如何实现吗? 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 "";
        }
    }
}

但是这个自定义 属性 显然不适用于字典/expando 对象。对这里的前进路线有什么想法吗?

您可以覆盖 TryGetMember method on 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;
    }
}

然后下面会输出访问的属性 names

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。这里仍然存在一个问题——这仅在模板库期望属性中只有 strings 时才有效。下面的代码会失败,因为每个 属性 都会 return 一个字符串:

DateTime dob = testObject.DOB;

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

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();
        }
    }
}