【问题标题】:How should I instantiate a class of unknown type?我应该如何实例化一个未知类型的类?
【发布时间】:2016-06-23 21:49:39
【问题描述】:

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

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

  • 控件用户必须做大量的工作来创建一个实现IPocoFactory 接口的类,以使用原本非常简单的控件
  • DataGrid 等控件已经解决了这个问题(尽管我似乎不知道如何解决,尽管我用 ILSpy 研究了一段时间!)

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

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

【问题讨论】:

    标签: c# wpf wpf-controls


    【解决方案1】:

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

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

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

    您需要声明类型的程序集but that's a property of TypeAssembly 也有 a CreateInstance method。这是做同样事情的另一种方法:

    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();
    

    注意

    这取决于实际集合是通用集合,但它不关心您对集合的reference 类型。 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;
    

    【讨论】:

    • 感谢您的详细回答...但不幸的是,它并没有完全勾选正确的框。正如我所说,该集合是非泛型的IEnumerable(在 WPF 控件中很常见,因为 XAML 难以处理类型参数),因此没有类型参数。仅当集合不为空并且从头开始创建集合是此控件的一流用例时,您的方法的非泛型部分才有效。
    • @BobSammers 如果集合的类型超出您的控制范围,则无能为力。信息不在那里。最好的办法是更改集合类型。为什么它们不是通用的?
    • @BobSammers 您确实还有一个选择:公开Type IPOCOType,而不是您的 IPOCOFactory 财产想法。这给消费者带来了更轻的负担:只需传递类型。从我上面的代码中,您可以只窃取一行来从Type 实例化实例。但是,如果该代码在您的控制之下,那么修复集合才是真正的答案。
    • 对不起,我误会了。如果控件绑定到的集合是通用的,那么即使控件本身中的 ItemsSource 不是通用的(这种情况也是如此)。我确实将您的代码复制到了一个测试应用程序中,但我一定是出于不同的原因得到了错误的答案!
    • @BobSammers 哦,对了,我要补充一点:GetType() 返回对象的实际运行时类型,而不管声明的引用类型如何。通用集合都支持System.Collections.IEnumerable,因此可以转换为它,但GetType() 上的IEnumerable 引用可能会返回各种好东西。很高兴听到它对您有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-13
    • 2019-05-20
    相关资源
    最近更新 更多