【问题标题】:DataTable into List<T> *without* where T : class, new() - potential problems?DataTable into List<T> *without* where T : class, new() - 潜在问题?
【发布时间】:2009-11-24 12:08:06
【问题描述】:

今天早上我一直在寻找一种从 System.Data.DataTable 创建特定对象列表的方法。到目前为止,我的 DomainModel 基类中有这个:

    protected static List<T> ConvertTo<T>(DataTable dt) where T : class, new()
    {
        int count = dt != null ? dt.Rows.Count : 0;
        List<T> list = new List<T>(count);

        if (dt != null & dt.Rows.Count > 0)
        {
            foreach (DataRow row in dt.Rows)
            {
                T item = new T(); // ????
                FillObjectFromDataRow(item, row);
                list.Add((T)item);
            }
        }

        return list;
    }

注意:我希望返回一个空列表,因为大多数情况下它只是绑定到数据网格、中继器等。

但是,它不起作用,因为类通常有一个私有构造函数来防止未经授权的实例化(我的意思是这样),所以我得到“类型'typename'必须是具有公共的非抽象类型无参数构造函数”错误。

我不愿意在我的类中引入公共无参数构造函数,因为它们包含 90% 的静态方法(与 ObjectDataSources 一起使用)并且创建一个“空”类是没有意义的。 IE。一个新的 Employee 对象将是空白的,而不是由 public static Employee Get(string employeeID) 创建的,后者将包含更多有用的信息。

关于如何在 '????' 处创建 T 的“事物”的任何想法标记行而不使用 new() 约束?我可以通过 Type parm 实例化一个“事物”吗?

感谢您的任何想法,只是尝试通过使用此功能在我的应用中干燥...

亲切的问候,

迈克·K。

PS 是的,也许 dt.Rows.Count > 0 调用是不必要的......

【问题讨论】:

  • FillObjectFromDataRow 是做什么的?是使用反射从行映射到项目还是具有某些类型的专业知识?如果是后者,那么FillObjectFromDataRow 就不能具备如何创建每种类型的专业知识吗?
  • 你好 Mike 二,它使用反射来映射行,所以它不是专门的。我更喜欢这种方式,尽可能通用(我认为,随时准备学习)。

标签: c# generics


【解决方案1】:

如前所述,Activator.CreateInstance 方法应该可以完成工作 - class, new() 是一种方便,并表达了我们知道应该的情况,当然……你有什么特别的理由不想要它吗?

另一种方法是传入工厂方法 - 这可能特别有助于解决任何 DI 问题等:

protected static List<T> ConvertTo<T>(DataTable dt, Func<T> ctor)
{
    {loop}
        T newItem = ctor();
        {fill row}
    {/loop}
}

然后您可以调用为(例如):

List<Person> people = ConvertTo<Person>(dataTable, () => new Person());

或:

List<Person> people = ConvertTo<Person>(
     dataTable, () => diContainer.Create<Person>()); // for your DI service

【讨论】:

  • 我不希望其他人创建“空白”对象,因为它们将毫无价值,即使用空类完成的操作很少(almnst none),请参阅我的原始帖子了解我将如何通过静态 GetPerson 调用检索人员。 (这不是一件坏事,是吗?)也就是说,您的工厂方法/erikkallen 的创建者 lambda 可能是要走的路,我会尽快尝试。我之前确实看到过类似的东西,但无法理解它;看到它与我的代码相关有帮助:-)
  • 太棒了,这行得通 - 阅读 Func 对我有所启发(“封装一个没有参数的方法并返回由 TResult 参数指定的类型的值”,例如 a构造函数),我只是对“()=> new Person()”语法有点模糊。单步执行基类中的代码,我看到它调用了 ctor 方法,然后该方法转到子类调用 () 方法,该方法返回一个新的 Person...啊,我想我明白了 :-)跨度>
  • 不,现在我真的明白了 - 太好了 :-) 感谢您的帮助,希望其他人阅读此主题,因为它是一个完整的、有效的示例。
【解决方案2】:

您可以使用Activator.CreateInstance&lt;T&gt;() 或通过typeof(T).GetConstructor(...).Invoke(new object[] { ... }); 调用构造函数

如果您需要大量此类操作;您也可以使用Expression.New 来创建它们;作为CreateInstance 和通过构造函数是相当昂贵的方法。


以基于表达式的方式执行此操作的示例。

    private void Foo()
    {
        TestJan myObject = CreateObject<TestJan>("Jan", 21);
    }

    private T CreateObject<T>(string name, int age)
    {
        //first get a reference to the ConstructorInfo
        //we know constructor has 2 params, string and int32. Names are not important.
        System.Reflection.ConstructorInfo ci = typeof(T).GetConstructor(new[] { typeof(string), typeof(int) });

        //we now have to define the types
        ParameterExpression stringParam = Expression.Parameter(typeof(string), "stringExp");
        ParameterExpression intParam = Expression.Parameter(typeof(int), "intExp");

        //create an expression
        NewExpression constructor = Expression.New(ci, stringParam, intParam);

        //wrap this in a lambda-expression, which returns basically
        //    (stringExp, intExp) => new T(stringExp, intExp);
        LambdaExpression lambda = Expression.Lambda(constructor, stringParam, intParam);

        //compile into delegate
        var constructorDelegate = (Func<string, int, T>)lambda.Compile();

        //invoke the delegate. Normally you would cache this in a static Dictionary<Type, Delegate>.
        //when you cache this, it's lightning fast. As it's just as fast as hard program
        //    (stringExp, intExp) => new T(stringExp, intExp);
        return constructorDelegate.Invoke(name, age);
    }

您当然可以根据参数等进行更改,我发现这是创建对象的最佳方式,因为它既快速又灵活。

【讨论】:

  • 查看我对 Dzmitry re Activator.CreateInstance 的回答 - 我没有尝试过 GetConstructor,但与您分享您对费用的担忧。
  • 如果您真的关心性能。您应该使用Expression.New 创建一个已编译的表达式树,然后将其包装在Expression.Lambda.Compile() 中,以获得与本机代码一样快的可重用委托。
  • @Jan,你是大人,我是小人——这超出了我的想象:-s 为了我的启迪,我可以看到一些伪代码/代码来说明你所描述的内容吗?这将对我和其他人有所帮助,谢谢:-)
  • 好的,谢谢你的代码。除了在我的原始帖子的上下文中使用它之外,您多久使用这种创建对象的方式?它与DI有关吗?纯粹是为了性能吗?啊,我学到了一些新东西,然后我只有更多的问题;-)
  • 我用这个 f.e.用于从字节数组中反序列化大量对象,从业务实体创建 API 实体等。基本上所有可以通过反射完成的事情;但是反射太慢的地方。您使用反射来创建一棵树(就像这里使用ConstructorInfo),并创建一个已编译的委托,存储在一个单例中。灵活快速。
【解决方案3】:

您可以使用Activator.CreateInstance&lt;T&gt;()

编译器使用 CreateInstance 泛型方法来实现由类型参数指定的类型的实例化。

【讨论】:

  • 不,我不能,它不适用于没有无参数构造函数的对象 - 我收到“没有为此对象定义无参数构造函数”运行时错误。 :-(
【解决方案4】:

反射解决方案的替代方案可能是使用创建者 lambda:

protected static List<T> ConvertTo<T>(DataTable dt, Func<DataRow, T> create) {
    int count = dt != null ? dt.Rows.Count : 0;
    List<T> list = new List<T>(count);
    if (dt != null & dt.Rows.Count > 0)
    {
        foreach (DataRow row in dt.Rows)
        {
            list.Add(create(row));
        }
    }
    return list;
}

然后你可以像这样调用它:

var result = ConvertTo(dt, row => CreateObjectFromRow(row));

【讨论】:

  • 赞成,因为它与 Marc 的方法类似,但很抱歉没有标记为答案,因为调用代码可能如下所示:return ConvertTo(_EmployeeDA.Select(companyID), () => new Employee ());这消除了在通话中调用 CreateObjectFromRow 的需要;此外,它还可以与 DI 一起使用。
  • 我的回答中的 CreateObjectFromRow 调用是作为构造对象的方法,可能使用以 DataRow 作为参数的静态工厂方法。或任何你想要的。
猜你喜欢
  • 2016-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多