简体   繁体   English

如何解决C#中泛型类型约束的局限性?

[英]How to Work Around Limitations in Generic Type Constraints in C#?

Okay I'm looking for some input, I'm pretty sure this is not currently supported in .NET 3.5 but here goes. 好的,我正在寻找一些输入,我很确定.NET 3.5目前不支持这个,但是这里有。

I want to require a generic type passed into my class to have a constructor like this: 我想要一个传递给我的类的泛型类型有一个像这样的构造函数:

new(IDictionary<string,object>)

so the class would look like this 所以班级看起来像这样

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

But the compiler doesn't support this, it doesn't really know what I'm asking. 但是编译器不支持这个,它并不真正知道我在问什么。

Some of you might ask, why do you want to do this? 有些人可能会问,你为什么要这样做? Well I'm working on a pet project of an ORM so I get values from the DB and then create the object and load the values. 好吧,我正在研究ORM的宠物项目,所以我从数据库中获取值,然后创建对象并加载值。

I thought it would be cleaner to allow the object just create itself with the values I give it. 我认为允许对象只使用我给它的值创建自己会更清晰。 As far as I can tell I have two options: 据我所知,我有两个选择:

1) Use reflection(which I'm trying to avoid) to grab the PropertyInfo[] array and then use that to load the values. 1)使用反射(我试图避免)获取PropertyInfo []数组,然后使用它来加载值。

2) require T to support an interface like so: 2)要求T支持这样的接口:

public interface ILoadValues { void LoadValues(IDictionary values); public interface ILoadValues {void LoadValues(IDictionary values); } }

and then do this 然后这样做

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

The problem I have with the interface I guess is philosophical, I don't really want to expose a public method for people to load the values. 我猜这个界面的问题是哲学的,我真的不想公开一种公共方法供人们加载值。 Using the constructor the idea was that if I had an object like this 使用构造函数的想法是,如果我有一个像这样的对象

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

As long as the MyClass<T> was in the same assembly the constructor would be available. 只要MyClass<T>在同一个程序集中,构造函数就可用。 I personally think that the Type constraint in my opinion should ask (Do I have access to this constructor? I do, great!) 我个人认为我认为Type约束应该问(我有权访问这个构造函数吗?我做的很棒!)

Anyways any input is welcome. 无论如何,欢迎任何输入。

As stakx has said, you can't do this with a generic constraint. 正如stakx所说,你不能用通用约束来做到这一点。 A workaround I've used in the past is to have the generic class constructor take a factory method that it can use to construct the T: 我过去使用的一种解决方法是让泛型类构造函数采用可用于构造T的工厂方法:

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

Used as follows: 使用如下:

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

This gives you compile time type safety, at the expense of a slightly more convoluted construction pattern, and the possibility that someone could pass you a delegate which doesn't actually create a new object (which may or may not be a major issue depending on how strict you want the semantics of CreateObject to be). 这给你编译时类型安全,代价是一个稍微复杂的构造模式,并且有人可能会传递给你一个实际上没有创建新对象的委托(这可能是也可能不是一个主要问题,取决于你想要CreateObject的语义有多严格。

If you can create common base class for all of T ojects that you are going to pass to MyClass as type parameters than you can do following: 如果您可以为要作为类型参数传递给MyClass的所有T对象创建公共基类,则可以执行以下操作:

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;
    }
}

If you cannot have common base class than I think you should go with solution proposed by itowlson. 如果你不能拥有共同的基类,我认为你应该选择itowlson提出的解决方案。

You cannot do that. 你不能这样做。 new (constructor) constraints are only for parameter-less constructors. new (构造函数)约束仅适用于无参数构造函数。 You cannot have a constraint for a constructor with specific parameters. 对于具有特定参数的构造函数,您不能有约束。

I've come across the same problem, though, and I've finally settled on doing the "injection" part through a method that is provided in one of the interfaces that is listed as a constraint (as demonstrated in your code). 但是,我遇到了同样的问题,我最终决定通过在其中一个作为约束列出的接口中提供的方法来执行“注入”部分(如代码中所示)。

(I hope someone here has found a more elegant answer to this problem!) (我希望有人在这里找到一个更优雅的答案!)

I'm legitimately curious at how you would load the values of a class without using reflection unless you had methods hardcoded to accomplish it. 我非常好奇你如何在不使用反射的情况下加载类的值,除非你有硬编码的方法来实现它。 I'm sure there's another answer, but I'm not too ashamed to say I do not have experience in it. 我确定还有另一个答案,但我不会羞于说我没有经验。 As for something I wrote to auto-load data, I have two base data classes I work from: a single object and then a list. 至于我写的自动加载数据的东西,我有两个我工作的基础数据类:一个对象然后一个列表。 In the single object (BaseDataClass), I have this method. 在单个对象(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;
                    }
                }
            }
        }
    }

And then in the data list 然后在数据列表中

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

I'm open to criticism, because no one has ever reviewed this code before. 我很容易接受批评,因为之前没有人审查过这段代码。 I am the sole programmer where I work, so I do not have others to bounce ideas off of. 我是我工作的唯一程序员,所以我没有其他人可以反复思考。 Thankfully, now I've come across this website! 值得庆幸的是,现在我遇到了这个网站!

Edit: You know what? 编辑:你知道吗? Looking at it now, I don't see why I shouldn't just rewrite that last method as 现在看一下,我不明白为什么我不应该只重写最后一个方法

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

I assume that works, although I haven't tested it. 我认为这有效,虽然我还没有测试过。 No need to use reflection on that part. 无需在该部分使用反射。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM