2009-05-12 18 views
17

Tengo una clase genérica de envoltura que se pretendía usar con un conjunto de tipos. Esos tipos son generados por una utilidad y se derivan de una clase base ClientBase. Si bien ClientBase solo tiene un constructor predeterminado, todos los tipos generados tienen un constructor predeterminado, así como un constructor toma una cadena como parámetro. En el constructor de la clase contenedora, instancia una instancia del tipo con el constructor que toma una cadena. Aquí es un ejemplo de código:¿Cómo restringir el tipo genérico a debe tener un construtor que toma ciertos parámetros?

public class ClientBase 
{ } 

public class GenericProxy<T> 
    where T: ClientBase, new() 
{ 
    T _proxy; 

    public GenericProxy(string configName) 
    { 
     _proxy = new T(configName);  
    } 
} 

Este código no se compila porque el tipo T no está garantizado para tener un constructor que toma una cadena. ¿Hay alguna manera de definir una restricción en la clase genérica para exigir que el tipo T tenga un constructor que tome una cadena? Si esto no es posible, ¿cuáles son las mejores alternativas para manejar este tipo de situaciones?

Respuesta

24

No es posible. Me gustaría ver "static interfaces" para manejar esto, pero no se lo espera en el corto plazo ...

Alternativas:

  • especificar un delegado para actuar como una fábrica para T
  • especifique otra interfaz para actuar como una fábrica para T
  • Especificar una interfaz en sí T para inicialización (y añadir una restricción para que T implementa la interfaz)

Th Los primeros dos son realmente equivalentes. Básicamente que le cambia su clase proxy a algo como esto: (. Asumo que va a querer crear más instancias más adelante - de otro modo que también podría pasar una instancia de T en el constructor)

public class GenericProxy<T> 
    where T: ClientBase, new() 
{ 
    string _configName; 
    T _proxy; 
    Func<string, T> _factory; 

    public GenericProxy(Func<string, T> factory, string configName) 
    { 
     _configName = configName; 
     _factory = factory; 
     RefreshProxy(); 
    } 

    void RefreshProxy() // As an example; suppose we need to do this later too 
    { 
     _proxy = _factory(_configName); 
    } 
} 

1

Como señala Jon, no hay compatibilidad incorporada para esto, pero aparte puede crear un delegado tipeado para el constructor (más rápido que la reflexión) usando Expression. El código para hacer esto se puede encontrar en MiscUtil (en MiscUtil.Linq.Extensions.TypeExt).

1

Esto no responde a su pregunta real, lo que limita un método, sino por la totalidad aquí es cómo se puede hacer lo que preguntas en tiempo de ejecución, utilizando la reflexión:

private T Get<T>(string id) 
    { 
    var constructor = typeof(T).GetConstructor(new Type[] { typeof(X), typeof(Y) }); 
    if (constructor == null) throw new InvalidOperationException("The type submitted, " + typeof(T).Name + ", does not support the expected constructor (X, Y)."); 

    var data = GetData(id); 
    return (T)constructor.Invoke(new object[] { data.x, data.y }); 
    } 
0

Aquí se basa un ejemplo de trabajo completa en @JonSkeet respuesta:

using System; 
using System.Collections.Generic; 

namespace GenericProxy 
{ 
    class Program 
    { 
     static void Main() 
     { 
      GenericProxy<ClientBase> proxy = new GenericProxy<ClientBase>(ClientBase.Factory, "cream"); 

      Console.WriteLine(proxy.Proxy.ConfigName); // test to see it working 
     } 
    } 

    public class ClientBase 
    { 
     static public ClientBase Factory(string configName) 
     { 
      return new ClientBase(configName); 
     } 

     // default constructor as required by new() constraint 
     public ClientBase() { } 

     // constructor that takes arguments 
     public ClientBase(string configName) { _configName = configName; } 

     // simple method to demonstrate working example 
     public string ConfigName 
     { 
      get { return "ice " + _configName; } 
     } 

     private string _configName; 
    } 

    public class GenericProxy<T> 
     where T : ClientBase, new() 
    { 
     public GenericProxy(Func<string, T> factory, string configName) 
     { 
      Proxy = factory(configName); 
     } 

     public T Proxy { get; private set; } 
    } 
} 

esperar para ver el resultado siguiente: ice cream

Cuestiones relacionadas