利用反射在构造函数中初始化属性列表

3

我正在尝试使用反射来初始化类(列表)中的所有属性:

public class EntitiesContainer
{
    public IEnumerable<Address> Addresses { get; set; }
    public IEnumerable<Person> People { get; set; }
    public IEnumerable<Contract> Contracts { get; set; }

    public EntitiesContainer()
    {
        var propertyInfo = this.GetType().GetProperties();
        foreach (var property in propertyInfo)
        {
            property.SetValue(property, Activator.CreateInstance(property.GetType()), null);
        }
    }
}

我遇到了异常:

没有为此对象定义不带参数的构造函数。

我将感激您提供的提示。


你需要在 CreateInstance 中传递一些参数。其中一个类型需要更多的信息。 - Daniel A. White
2
你不能创建接口的实例! - Daniel A. White
1
这似乎是一个不好的主意,与编译器作对... - Daniel A. White
我对反射还不熟悉。 - lissajous
如何实例化类型为 var typeOf = property.GetType().GenericTypeArguments[0] 的列表,以获取新的 List<typeOf>? - lissajous
@H.Wojtowicz 我在我的回答中讨论了这个问题: typeof(List<>).MakeGenericType(...) - Marc Gravell
4个回答

4

只要您将属性定义为具体类型,就可以实现此操作。这样做确实有效:

public class EntitiesContainer
{
    public List<Address> Addresses { get; set; }
    public List<Person> People { get; set; }
    public List<Contract> Contracts { get; set; }

    public EntitiesContainer()
    {
        var propertyInfo = this.GetType().GetProperties();
        foreach (var property in propertyInfo)
        {
            property.SetValue(this, Activator.CreateInstance(property.PropertyType));
        }
    }
}

由于IEnumerable<T>是一个接口,所以您无法创建其实例。

但是为什么您想要这样做呢?最好使用C#6中引入的自动属性初始化程序来初始化属性:

public class EntitiesContainer
{
    public IEnumerable<Address> Addresses { get; set; } = new List<Address>;
    public IEnumerable<Person> People { get; set; } = new List<Address>;
    public IEnumerable<Contract> Contracts { get; set; } = new List<Address>;
}

我有很多其他属性,而且它们可能随时发生变化,这就是为什么。 - lissajous
@H.Wojtowicz,当您添加新内容时,请遵循现有的模式。 - Daniel A. White

3
一般情况下,您需要创建的对象类型是 property.PropertyType;您想设置值的对象是 this,因此代码如下:
property.SetValue(this, Activator.CreateInstance(property.PropertyType), null);

但是!您的属性是IEnumerable<T>,而不是List<T> - 无法创建接口,只能创建具体类型。因此,您需要对泛型IEnumerable<Foo>进行大量解构为Foo(var args = type.GetGenericTypeArguments()),并构建List<Foo>(typeof(List<>).MakeGenericType(args))。或者只需将属性类型更改为List<T>!
坦率地说,最简单的方法就是执行以下操作:
public IEnumerable<Address> Addresses { get; set; } = new List<Address>();
public IEnumerable<Person> People { get; set; } = new List<Person>();
public IEnumerable<Contract> Contracts { get; set; } = new List<Contract>();

或者:

public List<Address> Addresses { get; } = new List<Address>();
public List<Person> People { get; } = new List<Person>();
public List<Contract> Contracts { get; } = new List<Contract>();

@H.Wojtowicz,您意识到使用反射方法会导致效率显著降低,并且难以维护,是吗?(请注意:反射确实可以变得非常高效,但需要更多的工作) - Marc Gravell

1
我想要实现的是在构造函数中调用以下类似方法的方法总结如下:
    private void InitializeAllCollections()
    {
        var properties = this.GetType().GetProperties();

        foreach (var property in properties)
        {
            var genericType = property.PropertyType.GetGenericArguments();
            var creatingCollectionType = typeof(List<>).MakeGenericType(genericType);
            property.SetValue(this, Activator.CreateInstance(creatingCollectionType));
        }
    }

感谢大家的帮助。:)


0
我有类似的需求:在为单元测试创建业务对象时,我希望将所有未初始化的列表默认为新列表,这样如果测试需要向列表中添加内容,我就不必担心在那里进行初始化。与 OP 一样,我有太多的业务对象需要更改为默认值。我的解决方案是其他方案的混合;例外情况是我只想要列表属性,并且仅当它们尚未初始化时才需要默认值。
    public static T DefaultLists<T>(this T obj)
    {
        var properties = obj.GetType().GetProperties().Where(q => q.PropertyType.Name == "List`1" && q.GetValue(obj) == null);
        foreach(var property in properties)
            property.SetValue(obj, Activator.CreateInstance(property.PropertyType));

        return obj;
    }

现在我的示例对象创建器可以返回新的businessObject.DefaultLists();


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接