【问题标题】:Setting Values on a derived class from the base class constructor using Reflection使用反射从基类构造函数设置派生类的值
【发布时间】:2011-07-21 19:17:40
【问题描述】:

我有两个这样的课程:

public abstract class MyBase
{
     protected MyBase(){
         Initialize();
     }

     protected IDictionary<string,string> _data;

     private void Initialize() {
         // Use Reflection to get all properties
         // of the derived class (e.g., call new MyDerived() then
         // I want to know the names "Hello" and "ID" here
         var data = GetDataFromBackend(propertyNamesFromDerived);
         _data = data;
     }
}

public class MyConcrete : MyBase
{
     public MyConcrete(){
         // Possibly use Reflection here
         Hello = _data["Hello"];
         ID = new Guid(data["ID"]);
     }

     public string Hello {get;set;}
     public Guid ID {get; set;}
}

如您所见,我希望我的基类的构造函数知道我正在实例化的派生类的属性。

现在,这似乎是一种巨大的代码气味,所以让我提供更多关于我的意图的背景,也许有更好的方法。

我有一个存储键/值对的后端系统,本质上是一个Dictionary&lt;string,string&gt;。我想抽象出使用这个后端系统的方式,人们可以创建属性是后端系统的键的类。当他们构造这个对象时,它会自动从那个系统加载数据并初始化所有的变量。

换句话说,我刚刚重新发明了序列化,只是我不控制后端系统,只是让使用它变得非常轻松。我不希望调用者在构造对象后必须调用 Initialize(),因为在 100% 的情况下,您必须在构造后初始化它。

我不想将初始化代码移动到派生类中,字符串到业务对象的转换除外。

我必须使用工厂吗?或者在基构造函数中查看派生类的属性 names 是否安全? (不要关心它们的值并且它们没有被初始化,只需要名称)。

或者有没有更好的方法来提供字符串字典和具体业务对象之间的外观?

编辑: 这是 .net 3.5,所以没有 System.Dynamic 会使这变得微不足道:(

编辑 2: 在查看了答案并进一步思考之后,我想我的问题现在真的可以归结为:从基础构造函数调用 GetType().GetProperties()为了获得属性的名称,以及它们是否用特定的属性装饰安全?

【问题讨论】:

    标签: c# .net reflection


    【解决方案1】:

    等一下,让我们在这里稍等片刻,并正确执行此操作。 MyBase 不应该这样做。

    因此,您编写了一个类来管理从后端获取内容,然后在该类上编写一个类似于

    的方法
    T Get<T>() where T : new()
    

    然后你让Get 负责从后端读取字典并使用反射来填充T 的实例。因此,你说

    var concrete = foo.Get<MyConcrete>();
    

    这并不难,而且是正确的方法。

    顺便说一句,Get 的代码看起来像

    T t = new T();
    var properties = typeof(T).GetProperties();
    foreach(var property in properties) {
        property.SetValue(t, dictionary[property.Name], null);
    }
    return t;
    

    dictionary 是您加载的键/值对。事实证明有更优化的方法可以做到这一点,但除非它是一个瓶颈,否则我不会担心它。

    【讨论】:

    • 谢谢。看完这个之后,我澄清了我的问题,因为我真正感兴趣的部分是GetType().GetProperties() 是否安全,只是为了获取名称和属性——不再关心值或在基类中设置它们。
    • @Michael Stum:定义“安全”。
    • 在语言和运行时的保证范围内。目前试图找出当 ctor 运行时 CLR 对对象状态的保证是什么。
    • @Michael Stum:你希望它保证什么?
    • @Michael Stum:我怎么强调都不应该在基类的构造函数中执行此操作,或者在基类之外的继承链上的任何其他位置执行此操作。当您说这是代码气味时,您是对的。 当你的冰箱闻起来难闻时,你会清理它,你不会盲目地伸手去拿一个发霉的三明治。
    【解决方案2】:

    更好的方法是让类直接使用字典:

    public string Hello {
        get { return (string)base.data["Hello"]; }
        set { base.data["Hello"] = value; }
    }
    

    您可能希望在 getter 中调用 TryGetValue,以便在密钥不存在时返回默认值。 (您可能应该在基类中的单独方法中这样做)

    您可以编写代码 sn-p 以使属性更易于创建。

    如果你不想这样做,你可以调用GetType().GetProperties()来获取你类中属性的PropertyInfo对象,然后调用SetValue(this, value)
    这会很慢;您可以使用各种技巧来加速它,使用表达式树、CreateDelegate 或 IL 生成。

    【讨论】:

    • 谢谢。看完这个之后,我澄清了我的问题,因为我真正感兴趣的部分是GetType().GetProperties() 是否安全,只是为了获取名称和属性——不再关心值或在基类中设置它们。
    • @Michael:是的,它是安全的。与 C++ 不同,对象是完全构造的;没有发生的只是派生 ctor 中的用户代码。您可以调用 GetType() 甚至将其转换为派生类型。
    【解决方案3】:

    【讨论】:

    • 这是否意味着我必须将 Initialize 抽象化并将管道移至 Concrete 类?我想避免这种情况
    • 不,例如,您可以添加一个抽象方法 listProperties,该方法在 Initialize 内部调用以准确查询所需的数据。
    【解决方案4】:

    您是否考虑过使用 ExpandoObject?有了它,您可以动态添加属性并检查它们(例如在序列化时)。

    【讨论】:

    • 非常好。忘了说:我在 .net 3.5 上,所以没有 Dynamic Awesome :(
    【解决方案5】:

    我不确定这是否是您真正应该做的,但这是您要求的(将其放入 Initialize,您将获得派生属性名称的列表):

            var derivedProps = this.GetType().GetProperties();
            var propNames = new List<string>(derivedProps.Select(x => x.Name));
    

    从那里,使用derivedProps 中的PropertyInfos,您可以设置属性。

    【讨论】:

      【解决方案6】:

      无论如何,您不能真正安全地对基类构造函数中的这些属性执行任何操作,因为某些派生构造函数可能会重置它们。你最好进行两阶段加载(例如显式调用 Initialize)

      【讨论】:

      • 谢谢。在看完这个之后,我澄清了我的问题,因为我真正感兴趣的部分是 GetType().GetProperties() 是否安全,只是为了获取名称和属性 - 不再关心值或在基类中设置它们。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2011-05-03
      • 2018-07-16
      • 2018-07-21
      • 2011-09-27
      • 2014-01-21
      • 2012-09-28
      相关资源
      最近更新 更多