我应该如何实例化一个未知类型的class?

How should I instantiate a class of unknown type?

我有一个 UserControl,它被设计用来编辑一些任意 POCO 的集合。 POCO 是在设计时选择的,因此我可以传递 POCO 中需要显示和编辑的属性的描述,但我正在努力寻找在控件中实例化新 POCO 以添加到集合中的最佳方法。

目前,我正在 运行 添加一个新的 属性 到包含 IPocoFactory 的控件,但这似乎并不令人满意,原因如下:

任何人都可以为这个问题提出一个合适的模式吗?我不可能是唯一一个面对它的人!

我想到反射可能在解决方案中发挥作用,但我对此也不太确定:我可以检查 ItemsSource(非通用 IEnumerable)看看里面有什么,但如果里面是空的,就没什么可看的了。

您可以通过调用 ItemsSource.GetType().GetInterfaces(), finding the Type object for the IEnumerable<T> interface (which any generic collection will implement), and calling GetGenericArguments() 来获取要创建的类型。 IEnumerable<T> 当然有一个类型参数,所以这是您需要创建实例的类型。

然后 you can create an instance fairly easily(请参阅下面的更新以了解将这一切包装到单个方法调用中的静态方法):

ObjectType instance = (ObjectType)Activator.CreateInstance("AssemblyName",
                                                           "MyNamespace.ObjectType");

您还需要声明类型的程序集,but that's a property of Type. Assembly has a CreateInstance method。这是做同样事情的另一种方法:

Type otype = typeof(ObjectType);
ObjectType instance = (ObjectType)otype.Assembly.CreateInstance(otype.FullName);

如果要实例化的类型没有默认构造函数,这会变得更难看。您必须编写明确的代码来提供值,而且没有办法保证它们有意义。但至少与 IPOCOFactory 实现的混乱相比,这给消费者带来的负担要轻得多。

请记住 System.String 没有默认构造函数。用 List<String> 测试下面的代码是很自然的,但那会失败。

获得 ItemsSource 中对象的类型后,您可以通过以编程方式枚举属性的名称和类型以及自动生成列来进一步简化维护。如果需要,您可以写一个 Attribute class 来控制显示哪些,提供显示名称等。

更新

这是一个粗略的实现,它可以让我创建在不同程序集中声明的 class 的实例:

/// <summary>
/// Collection item type must have default constructor
/// </summary>
/// <param name="items"></param>
/// <returns></returns>
public static Object CreateInstanceOfCollectionItem(IEnumerable items)
{
    try
    {
        var itemType = items.GetType()
                            .GetInterfaces()
                            .FirstOrDefault(t => t.Name == "IEnumerable`1")
                            ?.GetGenericArguments()
                            .First();

        //  If it's not generic, we may be able to retrieve an item and get its type. 
        //  System.Windows.Controls.DataGrid will auto-generate columns for items in 
        //  a non-generic collection, based on the properties of the first object in 
        //  the collection (I tried it).
        if (itemType == null)
        {
            itemType = items.Cast<Object>().FirstOrDefault()?.GetType();
        }

        //  If that failed, we can't do anything. 
        if (itemType == null)
        {
            return null;
        }

        return itemType.Assembly.CreateInstance(itemType.FullName);
    }
    catch (Exception ex)
    {
        return null;
    }
}

public static TestCreate()
{
    var e = Enumerable.Empty<Foo.Bar<Foo.Baz>>();

    var result = CreateInstanceOfCollectionItem(e);
}

如果您愿意,可以将 CreateInstanceOfCollectionItem() 设为 IEnumerable 的扩展方法:

var newItem = ItemsSource?.CreateInstanceOfCollectionItem();

注意

这取决于实际集合是否为通用集合,但它不关心您对集合的引用 的类型。 ItemsControl.ItemsSource 属于 System.Collections.IEnumerable 类型,因为任何标准泛型集合都支持该接口,因此可以转换为它。但是在非泛型接口引用上调用 GetType() 将 return 引用另一端(可以这么说)对象的实际运行时类型:

var ienumref = (new List<String>()) as System.Collections.IEnumerable;
//  fullName will be "System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
//  ...or something like it, for whatever version of .NET is on the host.
var fullName = ienumref.GetType().Name;