views:

277

answers:

4

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.

+1  A: 

You cannot do that. new (constructor) constraints are only for parameter-less constructors. 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!)

stakx
+6  A: 

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

itowlson
+1 (more if I could). This is a bit above my pay grade and I can't actually state with confidence that this is correct (which I'm sure it is). However, your example is extremely clear and quite easy to follow. Well said, sir!
Metro Smurf
+1  A: 

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.

Andrew Bezzub
I set you as the answer because I never knew you could hide an interface implementation by explicitly declaring it. Or at least it didn't click until you wrote it. Thanks!
Jose
+1  A: 

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.

Anthony Pegram
If your using DataSets or Typed DataSets, why wouldn't you use the MetaData in these, If your using a Typed dataSet, you sort of know what the data is before you start reflecting. Why use an ORM if your going to use DataSets?DataSets also have problems with large data, better off using a DataReader and streaming the data slowly keeping the memory allocation down to a minimum.
WeNeedAnswers
@Anthony Nice answer though, good to see that people care enough to give so complete an answer :)
WeNeedAnswers
I usually do not use DataSets, but rather read straight into a DataTable. I suppose I could use a reader, but I've always thought it was simpler if I needed to pass the object around to use a DataTable/DataRow rather than keep a database connection open. But, like I said, I'm open to criticism, and thanks for your feedback.
Anthony Pegram
@Anthony, not criticizing, sorry if it comes across as that, and certainly not patronising either. I get into trouble using Data as an Object, I usually run out of memory, especially when not connecting to an SQL DB (with filter in select) and using something like a csv file. Came across it once with a very large Spreadsheet, kept blowing the memory footprint. So switched to Readers and never looked back.
WeNeedAnswers
I don't use "criticism" as a negative term! I was thinking more along the lines of constructive criticism. Like I said, I'm looking for it.
Anthony Pegram
To answer your question on how I would load values without reflection. We have a code generation framework that generates classes based on a db schema. It's template based so I could generate the LoadValues method via the code generation.
Jose
by the way. (typeof(T))Activator.CreateInstance(typeof(T)); is about 4-5X faster than Activator.CreateInstance<T>();
Jose