【问题标题】:Store type information with DynamicObject使用 DynamicObject 存储类型信息
【发布时间】:2014-11-03 22:30:09
【问题描述】:

我正在尝试设计一个执行提取转换加载操作的流程。我想在我的管道中使用 ExpandoObject 以允许我轻松地将列添加到我的数据流中。基本上,我从某种数据源中读取数据,将其转换为动态数据并将其返回到转换管道,该转换管道基于现有属性或其他内容向其添加属性,然后将其流式传输到数据库中。

我遇到的问题是,即使我添加了 Nullable 类型,我也需要添加到我的 expando 对象的所有属性的类型信息。如果 Nullable 类型由于值的装箱而为 null,则会丢失。我想要类型信息,以便在管道结束时我可以在我的 ExpandoObjects 枚举上实现数据读取器并将数据流式传输到数据库中。

我曾希望 SetMemberBinder.ReturnType 属性可以帮助我,但它似乎返回了一个对象。

这里有一些示例代码:

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

using Xunit

namespace Whanger
{
    public class MyExpando : DynamicObject
    {
        Dictionary<string, object> properties = new Dictionary<string, object>();
        Dictionary<string, Type> propertyTypes = new Dictionary<string, Type>();

        public Dictionary<string, Type> Types
        {
            get
            {
                return propertyTypes;
            }
        }

        public Dictionary<string, object> Values
        {
            get
            {
                return properties;
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (properties.ContainsKey(binder.Name))
            {
                result = properties[binder.Name];
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            properties[binder.Name] = value;
            propertyTypes[binder.Name] = binder.ReturnType;//always object :(
            return true;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            dynamic method = properties[binder.Name];
            result = method(args[0].ToString(), args[1].ToString());
            return true;
        }
    }

    public class MyExpandoTests
    {
        [Fact]
        public void CanAddDynamicMembers()
        {
            dynamic obj = new MyExpando();
            obj.Name = "Wibble";
            obj.Value = 2;

            Assert.Equal(obj.Name, "Wibble");
            Assert.Equal(obj.Value, 2);
        }

        [Fact]
        public void CanMaintainType()
        {
            dynamic obj = new MyExpando();
            int? nullableInt = null;
            obj.NullInt = nullableInt;
            obj.Name = "Wibble";
            Assert.Equal(obj.Name, "Wibble");
            Assert.Null(obj.NullInt);
            //fails
            Assert.Equal(typeof(int?), ((MyExpando)obj).Types["NullInt"]);


        }
    }
}

有没有办法从 TrySetMember 中找出类型?我想知道是否有某种方法可以使用某种表达式树的魔法?

如果有人有什么好主意,我很想听听。除了可以为空的类型之外,这一切都很好,但它们是数据库操作的关键。

谢谢

【问题讨论】:

  • 虽然没有明确提及,但这可能会引起人们的兴趣:System.Dynamic.ExpandObject
  • ExpandoObject 只是一个对象字典,可为空的结构也会被装箱。

标签: c# dynamic nullable dynamicobject


【解决方案1】:

是的,有可能。

我对类型信息是否存储在某处进行了一些研究,我发现在设置成员期间,有一个Func&lt;System.Runtime.CompilerServices.CallSite,object,int?,object&gt; 对象正在使用。这可能用于存储绑定以供以后使用。

但是,这个缓存实际上是传递给 binder: private CallSiteBinder.Cache 字段的。它包含一个IDictionary&lt;Type,object&gt;,其中包含一个缓存委托类型作为键和委托本身。因此,通过检查委托类型的泛型参数,您可以获得赋值中使用的表达式的类型。

完整方法:

private static readonly FieldInfo CallSiteBinder_Cache = typeof(CallSiteBinder).GetField("Cache", BindingFlags.NonPublic | BindingFlags.Instance);
private static Type BindingType(CallSiteBinder binder)
{
    IDictionary<Type,object> cache = (IDictionary<Type,object>)CallSiteBinder_Cache.GetValue(binder);
    Type ftype = cache.Select(t => t.Key).FirstOrDefault(t => t != null && t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Func<,,,>));
    if(ftype == null) return null;
    Type[] genargs = ftype.GetGenericArguments();
    return genargs[2];
}

此方法获取秘密 Cache 字典并找到从 Func&lt;T1,T2,T3,TResult&gt; 构造的 Type 键。然后就是提取类型参数。

【讨论】:

    【解决方案2】:

    你不能添加一个重载:

    public override bool TrySetMember<T>(SetMemberBinder binder, Nullable<T> value)
    {
        properties[binder.Name] = value;
        propertyTypes[binder.Name] = typeof(Nullable<T>);
        return true;
    }
    

    【讨论】:

    • 除非有基类方法可以覆盖,否则不能覆盖。请注意,在提供的代码中,没有对TrySetMember 的显式调用,只有编译器合成的调用。
    • 我的意思是 MyExpando.TrySetMember 上的重载(不是 ExpandoObject.TrySetMember 上的覆盖)...
    • 您的评论与代码中的 override 关键字有所不同。
    • TrySetMember 方法由运行时绑定器调用,而不是由用户调用。
    【解决方案3】:

    这是一个很好的问题,但没有通用的解决方案。 object 参数无法检测传递的值是否先前存储在 Nullable&lt;T&gt; 变量中。看来您将不得不等到您有多个记录,然后查看是否有 null 和值的混合。

    在这里的具体情况下,我会检查binder 参数上的ReturnType 属性,看看它是否能告诉你你需要什么。我看到你已经检查过了。

    您的一般问题是您所拥有的只是值及其类型,而不是表达式的静态类型。例如,您也无法区分这些情况:

    string s = "hello";
    object o = s;
    dynamo.P = s; // case 1
    dynamo.P = o; // case 2
    

    甚至

    dynamo.Use(s);
    dynamo.Use(o);
    

    非常不同于静态类型语言,其中表达式的类型用于重载决议。看来DynamicObject 不可能做到这种事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-13
      • 2010-12-18
      相关资源
      最近更新 更多