2010-03-20 66 views
6

好吧我正在寻找一些输入,我很确定这是目前不支持在.NET 3.5中,但在这里。如何解决C#中泛型类型约束的局限性?

我想需要一个泛型类型传递到我的同班同学有这样的构造:

new(IDictionary<string,object>) 

因此该类看起来像这样

public MyClass<T> where T : new(IDictionary<string,object>) 
{ 
    T CreateObject(IDictionary<string,object> values) 
    { 
    return new T(values); 
    } 
} 

但是编译器不支持这,它并不真正知道我在问什么。

你们有些人可能会问,你为什么要这么做?那么我正在开发一个ORM的宠物项目,所以我从数据库中获取值,然后创建对象并加载值。

我认为这将是更清洁,让对象只是用我给它的价值创造自己。据我可以告诉我有两个选项:

1)使用反射(我试图避免)获取PropertyInfo []数组,然后用它来加载值。

2)要求吨至支持像这样的接口:

公共接口ILoadValues { 空隙LoadValues(IDictionary的值); }

,然后做这个

public MyClass<T> where T:new(),ILoadValues 
{ 
    T CreateObject(IDictionary<string,object> values) 
    { 
    T obj = new T(); 
    obj.LoadValues(values); 
    return obj; 
    } 
} 

我有我的猜测是哲学接口的问题,我真的不希望暴露人装载值的公共方法。使用构造的想法是,如果我有一个对象像这样

namespace DataSource.Data 
{ 
    public class User 
    { 
    protected internal User(IDictionary<string,object> values) 
    { 
     //Initialize 
    } 
    } 
} 

只要MyClass<T>是在同一程序的构造函数可用。我个人认为在我看来,类型约束应该问(我可以访问这个构造函数吗?我的确很棒!)

反正任何输入是受欢迎的。

回答

2

如果你可以创建所有的T ojects的,你会传递给MyClass的类型参数比你可以做以下的公共基类:

internal interface ILoadValues 
{ 
    void LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values); 
} 

public class Base : ILoadValues 
{ 
    void ILoadValues.LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values) 
    { 
     // Load values. 
    } 
} 

public class MyClass<T> 
    where T : Base, new() 
{ 
    public T CreateObject(IDictionary<string,object> values) 
    { 
     ILoadValues obj = new T(); 
     obj.LoadValues(values); 
     return (T)obj; 
    } 
} 

如果你不能拥有共同的基类,比我想象你应该使用itowlson提出的解决方案。

+0

我设置你为答案,因为我从来不知道你可以通过显式声明它来隐藏一个接口实现。或者至少它没有点击直到你写下它。谢谢! – Jose 2010-03-22 15:37:45

1

你不能那样做。 new(构造函数)约束只适用于无参数构造函数。您不能对具有特定参数的构造函数进行约束。

虽然我遇到了同样的问题,但我终于通过在作为约束列出的其中一个接口中提供的方法来完成“注入”部分的决定(如您在码)。

(我希望有人在这里发现了一个更优雅的回答这个问题!)

8

正如stakx说,你不能与通用约束做到这一点。我已经在过去使用的解决方法是让通用类的构造函数需要,它可以用来构造T A工厂方法:

public class MyClass<T> 
{ 
    public delegate T Factory(IDictionary<string, object> values); 

    private readonly Factory _factory; 

    public MyClass(Factory factory) 
    { 
    _factory = factory; 
    } 

    public T CreateObject(IDictionary<string, object> values) 
    { 
    return _factory(values); 
    } 
} 

使用方法如下:

MyClass<Bob> instance = new MyClass<Bob>(dict => new Bob(dict)); 
Bob bob = instance.CreateObject(someDictionary); 

这给了你编译时间类型的安全性,代价是稍微更复杂的构造模式,以及有人可能会传递给你一个实际上并没有创建新对象的代理(这可能是也可能不是主要问题,这取决于你的严格程度希望CreateObject的语义)。

+0

+1(如果可以,更多)。这有点高于我的薪酬等级,我实际上不能确信这是正确的(我确信这是正确的)。然而,你的例子非常清楚,很容易遵循。那么说,先生! – 2010-03-21 00:11:06

1

我很好奇你如何加载一个类的值而不使用反射,除非你有硬编码的方法来完成它。我确信还有另外一个答案,但我并不感到羞愧,说我没有经验。至于我编写的自动加载数据,我有两个基础数据类:单个对象和列表。在单个对象(BaseDataClass)中,我有这个方法。

public virtual void InitializeClass(DataRow dr) 
    { 
     Type type = this.GetType(); 
     PropertyInfo[] propInfos = type.GetProperties(); 

     for (int i = 0; i < dr.ItemArray.GetLength(0); i++) 
     { 
      if (dr[i].GetType() != typeof(DBNull)) 
      { 
       string field = dr.Table.Columns[i].ColumnName; 
       foreach (PropertyInfo propInfo in propInfos) 
       { 
        if (field.ToLower() == propInfo.Name.ToLower()) 
        { 
         // get data value, set property, break 
         object o = dr[i]; 
         propInfo.SetValue(this, o, null); 
         break; 
        } 
       } 
      } 
     } 
    } 

然后在数据列表

public abstract class GenericDataList<T> : List<T> where T : BaseDataClass 
{ 
    protected void InitializeList(string sql) 
    { 
     DataHandler dh = new DataHandler(); // my general database class 
     DataTable dt = dh.RetrieveData(sql); 
     if (dt != null) 
     { 
      this.InitializeList(dt); 
      dt.Dispose(); 
     } 
     dt = null; 
     dh = null; 
    } 

    protected void InitializeList(DataTable dt) 
    { 
     if (dt != null) 
     { 
      Type type = typeof(T); 
      MethodInfo methodInfo = type.GetMethod("InitializeClass"); 

      foreach (DataRow dr in dt.Rows) 
      { 
       T t = Activator.CreateInstance<T>(); 
       if (methodInfo != null) 
       { 
        object[] paramArray = new object[1]; 
        paramArray[0] = dr; 
        methodInfo.Invoke(t, paramArray); 
       } 

       this.Add(t); 
      } 
     } 
    } 
} 

我接受批评,因为没有人以往审核该代码。我是我工作的唯一程序员,所以我没有其他人可以将想法反弹出来。谢天谢地,现在我遇到了这个网站!

编辑:你知道吗?现在看它,我不明白为什么我不应该只重写最后方法

 protected void InitializeList(DataTable dt) 
     { 
      if (dt != null) 
      { 
       Type type = typeof(T); 

       foreach (DataRow dr in dt.Rows) 
       { 
        T t = Activator.CreateInstance<T>(); 
        (t as BaseDataClass).InitializeClass(dr); 

        this.Add(t); 
       } 
      } 
     } 

我认为作品,虽然我没有测试它。不需要在那部分上使用反射。

+0

如果您使用DataSet或Typed DataSet,为什么不在这些元数据中使用MetaData?如果您使用Typed dataSet,您在开始反思之前就知道数据是什么。如果你打算使用DataSets,为什么要使用ORM? 数据集在大数据方面也存在问题,最好使用DataReader并缓慢地对数据进行流式处理,以尽量减少内存分配。 – WeNeedAnswers 2010-03-21 01:15:07

+0

@Anthony虽然很好,但很高兴看到人们关心的足以给出如此完整的答案:) – WeNeedAnswers 2010-03-21 01:26:06

+0

我通常不使用DataSets,而是直接读入DataTable。我想我可以使用一个阅读器,但是我一直认为如果我需要传递该对象来使用DataTable/DataRow而不是保持数据库连接打开,则会更简单。但是,正如我所说的,我愿意接受批评,并感谢您的反馈。 – 2010-03-21 01:29:39