开发者

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

开发者 https://www.devze.com 2022-12-24 15:58 出处:网络
Okay I\'m looking for some input, I\'m pretty sure this is not currently supported in .NET 3.5 but here goes.

Okay I'm looking for some input, I'm pretty sure this is not currently supported in .NET 3.5 but here goes.

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.

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.

2) require T to support an interface like so:

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. I personally think that the Type constraint in my opinion should ask (Do I have access to this constructor? I do, great!)

Anyways any input is welcome.


As stakx has said, you can't do this with a generic constraint. 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:

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).


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:

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.


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.

    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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号