2010-03-20 11 views
6

Bien, estoy buscando algo de entrada, estoy bastante seguro de que esto no es actualmente compatible con .NET 3.5, pero aquí va.Cómo evitar limitaciones en restricciones de tipo genérico en C#?

Quiero requerir un tipo genérico pasado a mi clase a tener un constructor de esta manera:

new(IDictionary<string,object>) 

por lo que la clase se vería así

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

Sin embargo, el compilador no admite esto, realmente no sabe lo que estoy preguntando.

Algunos de ustedes pueden preguntar, ¿por qué quieren hacer esto? Bueno, estoy trabajando en un proyecto favorito de un ORM, así que obtengo valores de la BD y luego creo el objeto y cargo los valores.

Pensé que sería más limpio permitir que el objeto simplemente se cree a sí mismo con los valores que le doy. Por lo que puedo decir, tengo dos opciones:

1) Utilice la reflexión (que estoy tratando de evitar) para tomar la matriz PropertyInfo [] y luego usar eso para cargar los valores.

2) requieren T para soportar una interfaz de este modo:

ILoadValues ​​interfaz pública { LoadValues ​​nulos (valores IDictionary); }

y luego hacer esto

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

El problema que tengo con la interfaz supongo que es filosófico, yo realmente no quiero exponer un método público para que la gente se cargan los valores. Utilizando el constructor la idea era que si tuviera un objeto como éste

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

Mientras el MyClass<T> estaba en la misma Asamblea el constructor estaría disponible. Personalmente creo que la restricción de tipo en mi opinión debería preguntar (¿Tengo acceso a este constructor? ¡Lo hago, genial!)

De todos modos cualquier entrada es bienvenida.

Respuesta

2

Si usted puede crear la clase base común para todos oyectos T que se va a pasar a MiClase como parámetros de tipo de lo que puede hacer lo siguiente:

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

Si no se puede tener clase base común de lo que pienso deberías ir con la solución propuesta por itowlson.

+0

Lo configuré como la respuesta porque nunca supe que podría ocultar una implementación de interfaz al declararla explícitamente. O al menos no hizo clic hasta que lo escribió. ¡Gracias! – Jose

1

No puede hacer eso. Las restricciones new (constructor) son solo para constructores sin parámetros. No puede tener una restricción para un constructor con parámetros específicos.

Me he encontrado con el mismo problema, y ​​finalmente me he decidido a hacer la parte "inyección" a través de un método que se proporciona en una de las interfaces que se enumera como una restricción (como se demuestra en su código).

(espero que alguien aquí ha encontrado una respuesta más elegante a este problema!)

8

Como stakx ha dicho, no se puede hacer esto con una limitación genérica.Una solución que he usado en el pasado es que el constructor de la clase genérica toma un método de fábrica que se puede utilizar para construir el 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); 
    } 
} 

Se utiliza de la siguiente manera:

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

Esto le da compile el tipo de seguridad del tiempo, a expensas de un patrón de construcción un poco más intrincado, y la posibilidad de que alguien le pase un delegado que no crea realmente un nuevo objeto (que puede o no ser un problema importante dependiendo de qué tan estricto quiere que sea la semántica de CreateObject).

+0

+1 (más si pudiera). Esto está un poco por encima de mi grado de pago y realmente no puedo afirmar con confianza que esto es correcto (que estoy seguro es). Sin embargo, su ejemplo es extremadamente claro y bastante fácil de seguir. Bien dicho, señor! –

1

Tengo una curiosidad legítima sobre cómo cargaría los valores de una clase sin usar la reflexión a menos que tuviera métodos codificados para lograrlo. Estoy seguro de que hay otra respuesta, pero no me da vergüenza decir que no tengo experiencia en eso. En cuanto a algo que escribí para cargar datos automáticamente, tengo dos clases de datos base desde las que trabajo: un solo objeto y luego una lista. En el único objeto (BaseDataClass), tengo este método.

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

Y luego, en la lista de datos

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

estoy abierto a las críticas, porque nunca nadie ha revisado este código antes. Soy el único programador en el que trabajo, por lo que no tengo otros para compartir ideas. Afortunadamente, ¡ahora he encontrado este sitio web!

Editar: ¿Sabes qué? Mirándolo ahora, no veo por qué no debería simplemente reescribir ese último método que

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

Asumo que las obras, aunque no he probado. No es necesario usar la reflexión en esa parte.

+0

Si usa DataSets o Typed DataSets, ¿por qué no usaría MetaData en estos? Si utiliza un conjunto de datos Typed, sabrá qué datos son antes de comenzar a reflejar. ¿Por qué usar un ORM si va a usar DataSets? Los DataSets también tienen problemas con los datos grandes, es mejor utilizar un DataReader y transmitir los datos lentamente, reduciendo al mínimo la asignación de memoria. – WeNeedAnswers

+0

@Anthony Buena respuesta, es bueno ver que las personas se preocupan lo suficiente como para dar una respuesta tan completa :) – WeNeedAnswers

+0

Normalmente no uso DataSets, sino que leo directamente en una DataTable. Supongo que podría usar un lector, pero siempre he pensado que era más simple si necesitaba pasar el objeto para usar un DataTable/DataRow en lugar de mantener abierta una conexión de base de datos. Pero, como dije, estoy abierto a la crítica y gracias por sus comentarios. –

Cuestiones relacionadas