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

时间:2016-06-22 17:15:43

标签: c# wpf wpf-controls

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

目前,我正在向持有IPocoFactory的控件添加新属性,但由于以下几个原因,这似乎并不令人满意:

  • 控制用户必须做很多工作才能创建一个实现IPocoFactory接口的类,只是为了使用控件,否则会非常简单
  • 像DataGrid这样的控件已经解决了这个问题(虽然我似乎无法弄清楚如何使用ILSpy探讨一段时间!)

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

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

1 个答案:

答案 0 :(得分:2)

您可以通过调用ItemsSource.GetType().GetInterfaces()来获取要创建的类型,找到Type接口的IEnumerable<T>对象(任何通用集合将实现该对象),并调用{{3} } 在上面。当然,IEnumerable<T>有一个类型参数,因此它是您创建实例所需的类型。

然后GetGenericArguments()(请参阅下面的UPDATE,了解一个静态方法,将所有内容包装到单个方法调用中):

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

您需要声明类型的程序集you can create an instance fairly easilyAssembly也有but that's a property of Type。这是另一种做同样事情的方法:

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

如果要实例化的类型没有默认构造函数,则会变得更加丑陋。您必须编写显式代码来提供值,并且无法保证它们有任何意义。但至少对于消费者施加的负担要轻得多,而不是一堆IPOCOFactory实施。

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

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

更新

这是一个粗略的实现,它为我创建了一个在不同程序集中声明的类的实例:

/// <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()将返回引用的另一端(可以这么说)对象的实际实际运行时类型:

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;