2010-12-01 21 views
5

Tengo algunos problemas graves de diseño debido a problemas con los genéricos. Tal vez alguien tiene algunas sugerencias.Conversión de tipo genérico

EDITAR: Entonces, sé que esto no suele hacerse, pero he cambiado completamente mi código de ejemplo, porque me he dado cuenta de que el pseudocódigo original realmente no explicaba mi problema. El siguiente código se parece mucho más al ejemplo real al que me refiero. Espero que mi problema sea más claro a partir de ahí. Me disculpo por adelantado porque es un poco largo, pero según mi experiencia, los problemas con los genéricos generalmente aparecen cuando intentas construir una estructura más compleja. Por lo tanto:

class Program 
    { 
     static void Main(string[] args) 
     { 
      IConnector<IService> connector = ConnectorBuilderFactory.NewBuilder<IService>("someEndpoint").MakeReliable().GetConnector(); 
      connector.Connect(); 
     } 
    } 

    public interface IService : IConnectionMaintainable 
    { 
     void DoSomething(); 
    } 

    public interface IConnectionMaintainable 
    { 
     DateTime GetServerTime(); 
    } 

    public interface IConnector<T> 
    { 
     T Channel { get; } 
     void Connect(); 
     void Disconnect(); 
    } 

    public interface IConnectorBuilder<T> 
    { 
     IConnector<T> GetConnector(); 
     IConnectorBuilder<T> MakeReliable(); 
     // ...more connector-configuration methods 
    } 

    public class ChannelWatchDog<T> where T : IConnectionMaintainable 
    { 
     private IConnector<T> connector; 

     public ChannelWatchDog(IConnector<T> connector /*various other parameters*/) 
     { 
      this.connector = connector; 
     } 

     // ...methods that use connector's Connect, Disconnect, and GetServerTime methods 
    } 

    public class Connector<T> : IConnector<T> 
    { 
     private T channel; 

     public Connector(string endpoint) 
     { 
      // ...build channel 
     } 

     public T Channel 
     { 
      get { return channel; } 
     } 

     public void Connect() 
     { 
      // ...connect to server 
     } 

     public void Disconnect() 
     { 
      // ...disconnect from server 
     } 
    } 

    public class ConnectorBuilder<T> : IConnectorBuilder<T> 
    { 
     private string endpoint; 

     public ConnectorBuilder(string endpoint) 
     { 
      this.endpoint = endpoint; 
     } 

     public IConnector<T> GetConnector() 
     { 
      Connector<T> connector = new Connector<T>(endpoint); 

      // If reliability was requested, build the ChannelWatchDog: Following line does not compile: 
      // ChannelWatchDog<T> watchDog = new ChannelWatchDog<T>(connector); 

      return connector; 
     } 

     public IConnectorBuilder<T> MakeReliable() 
     { 
      // save various parameters required to build the ChannelWatchDog 
      return this; 
     } 
    } 

    public static class ConnectorBuilderFactory 
    { 
     public static IConnectorBuilder<T> NewBuilder<T>(string endpoint) 
     { 
      return new ConnectorBuilder<T>(endpoint); 
     } 
    } 

Así, en primer lugar, si usted encuentra el método GetConnector en la clase ConnectorBuilder, verá la línea comentada de código, que no se compila si no comentada. Esta línea es la esencia de mi problema. El problema podría ser obvio a partir del código, pero voy a tratar de explicar de todos modos en caso de que no lo es:

  1. que tienen una clase interna (ChannelWatchDog) que necesita una IConnector. Pero no solo cualquier IConnector, un IConnector, porque aparte de los métodos de IConnector no genéricos, también necesita el método GetServerTime de la interfaz IConnectionMaintainable.

  2. Para simplificar la construcción de conectores, esperaba implementar un generador usando el patrón Expression Builder (la interfaz IConnectionBuilder). Sin embargo, quiero ser capaz de construir cualquier IConnector, no solo IConnector <IConnectionMaintainable>. Por lo tanto, no puedo restringir T en IConnectorBuilder de la misma forma que lo limito para ChannelWatchDog. Al carecer de esta restricción, no tengo forma de compilarla cuando se llama a GetConnector. Agregar la restricción al método MakeReliable no ayuda.

Así que, esencialmente la razón por la que envió esta pregunta era que quería hacer algo que es aparentemente imposible. Yo quería que la ChannelWatchDog y la clase ConnectorBuilder a ser algo como esto:

public class ChannelWatchDog 
    { 
     private IConnector<IConnectionMaintainable> connector; 

     public ChannelWatchDog(IConnector<IConnectionMaintainable> connector /*various other parameters*/) 
     { 
      this.connector = connector; 
     } 

     // ...methods that use connector's Connect, Disconnect, and GetServerTime methods 
    } 

    public class ConnectorBuilder<T> : IConnectorBuilder<T> 
    { 
     private string endpoint; 

     public ConnectorBuilder(string endpoint) 
     { 
      this.endpoint = endpoint; 
     } 

     public IConnector<T> GetConnector() 
     { 
      Connector<T> connector = new Connector<T>(endpoint); 

      // If reliability was requested, build the ChannelWatchDog: Following line does not compile: 
      ChannelWatchDog watchDog = new ChannelWatchDog((IConnector<IConnectionMaintainable>)connector); 

      return connector; 
     } 

     public IConnectorBuilder<TReliable> MakeReliable<TReliable>() where TReliable : T, IConnectionMaintainable 
     { 
      // save various parameters required to build the ChannelWatchDog 
      return (IConnectorBuilder<TReliable>)this; 
     } 
    } 

Pero el elenco de IConnector falla en tiempo de ejecución.

De modo que fue mucho más largo de lo que había pensado originalmente. Si has llegado tan lejos en la lectura, entonces ya tienes mi agradecimiento :) Cualquier idea es bienvenida, incluida la reestructuración del código.

BTW, Al no haber encontrado una solución para esto, creé diferentes ConnectorBuilders (en este caso, un ReliableConnectorBuilder) y diferentes métodos de fábrica en la fábrica. Pero no me gusta mucho esta solución.

EDIT: Solo para aclarar y reiterar: No puedo restringir el IConnector ni el ConnectionBuilder ya que estos necesitan para apoyar los casos en los que no se implementa la interfaz IConnectionMaintainable.

+0

¿Cuál es el error de compilación en la línea que no se compila y ... qué línea es la que falla en el lanzamiento en tiempo de ejecución? tiene 2 preguntas diferentes (de hecho 2 diferentes) publicadas aquí. ¿cual es cual? ¿También puede tratar de concluir la pregunta con una pregunta real en lugar de simplemente declaraciones? – Maslow

+0

@Maslow, la línea comentada no se compila porque el conector no está restringido según lo requerido por ChannelWatchDog. Mi esperada solución para esto, que es el conector de fundición <T> al conector <IConnectionMaintainable> es lo que falla en el tiempo de ejecución. Es decir, la línea que en el primer ejemplo no compila, es la misma línea que en el segundo ejemplo, después de la modificación, arroja una excepción en el tiempo de ejecución. Y no, no son 2 preguntas separadas. Es una pregunta en la que proporciono uno de mis intentos de solución que falló en tiempo de ejecución. – joniba

Respuesta

5

¿El código para la interfaz?

GenericClass<IFoo> wrapper = new GenericClass<IFoo>(new FooImplementor()); 
Acceptor acceptor = new Acceptor(wrapper); 
+0

Sí, esa es la solución obvia. Es solo que no me explico todo muy bien :) Tan pronto como sea libre, actualizaré mi pregunta, ya que es un poco más complicada de lo que parece. – joniba

+0

Disculpe la demora. He reescrito la pregunta. Sé que es bastante largo, pero agradecería cualquier aporte. – joniba

0

Usted podría tener GenericClass<T> implemento/extender una clase de interfaz/base no genérica, al igual que en el caso de las listas y enumerables en .NET. El tipo genérico real podría ser un método/propiedad abstracta que devuelva un objeto Type e implementado en la clase genérica.

1

Lo que quiere para su GenericClass se llama Covarianz.Mire this para más detalles. Me whould voy por la respuesta de Marc, pero si esto no es lo que queremos es, a continuación, intente esto:

class GenericClass<out T> { ... } 
+0

Muy interesante. Pero estoy trabajando en .net 3.5 y no tengo opción de actualizar. – joniba

0

Si esta línea no compilar es el problema principal:

// If reliability was requested, build the ChannelWatchDog: Following line does not compile: 
     ChannelWatchDog watchDog = new ChannelWatchDog((IConnector<IConnectionMaintainable>)connector); 

entonces creo que debería ser

ChannelWatchDog watchDog = new ChannelWatchDog(connector); 

excepto que el yeso no en tiempo de ejecución, porque creo que tendría que ser limitado el IConnector:

public interface IConnector<T> where T:IConnectionMaintainable 
{ 
    T Channel { get; } 
    void Connect(); 
    void Disconnect(); 
} 

Esto es de moda y, con suerte, ofrece información o sugerencias. La restricción se compila de acuerdo con linqpad.

también da lugar a requisitos de restricción en cascada así que aquí está el resto del código que se compila:

void Main() 
{ 

} 

// Define other methods and classes here 
    public interface IConnectionMaintainable 
{ 
    DateTime GetServerTime(); 
} 

public interface IConnector<T> where T:IConnectionMaintainable 
{ 
    T Channel { get; } 
    void Connect(); 
    void Disconnect(); 
} 

public interface IConnectorBuilder<T> where T:IConnectionMaintainable 
{ 
    IConnector<T> GetConnector(); 
    IConnectorBuilder<T> MakeReliable(); 
    // ...more connector-configuration methods 
} 
public class ChannelWatchDog 
{ 
    private IConnector<IConnectionMaintainable> connector; 

    public ChannelWatchDog(IConnector<IConnectionMaintainable> connector /*various other parameters*/) 
    { 
     this.connector = connector; 
    } 

    // ...methods that use connector's Connect, Disconnect, and GetServerTime methods 
} 

public class ConnectorBuilder<T> : IConnectorBuilder<T> where T:IConnectionMaintainable 
{ 
    private string endpoint; 

    public ConnectorBuilder(string endpoint) 
    { 
     this.endpoint = endpoint; 
    } 
    public IConnectorBuilder<T> MakeReliable() 
    { 
     // save various parameters required to build the ChannelWatchDog 
     return this; 
    } 
    public IConnector<T> GetConnector() 
    { 
     Connector<T> connector = new Connector<T>(endpoint); 

     // If reliability was requested, build the ChannelWatchDog: Following line does not compile: 
     ChannelWatchDog watchDog = new ChannelWatchDog((IConnector<IConnectionMaintainable>)connector); 

     return connector; 
    } 

    public IConnectorBuilder<TReliable> MakeReliable<TReliable>() where TReliable : T, IConnectionMaintainable 
    { 
     // save various parameters required to build the ChannelWatchDog 
     return (IConnectorBuilder<TReliable>)this; 
    } 
} 

public class Connector<T> : IConnector<T> where T:IConnectionMaintainable 
{ 
    private T channel; 

    public Connector(string endpoint) 
    { 
     // ...build channel 
    } 

    public T Channel 
    { 
     get { return channel; } 
    } 

    public void Connect() 
    { 
     // ...connect to server 
    } 

    public void Disconnect() 
    { 
     // ...disconnect from server 
    } 
} 
+0

Sí, puede ver en la publicación original que lo que terminé haciendo era similar a esto: creé un ConnectionBuilder adicional donde T está restringido. Pero mi intención con esta pregunta era ver si había una solución diferente, con suerte más elegante. – joniba

0

usted debe hacer el parámetro T genérico en el ConnectorBuilder clase implemente la interfaz IConnectionMaintainable. La razón de esto es que es necesaria para el tipo genérico de ChannelWatchDog. El T genérico no es lo suficientemente estricto para el parámetro de tipo genérico de ChannelWatchDog.

1

Es posible utilizar el reflejo para permitir el lanzamiento de Connector<T> a Connector<IConnectionMaintainable>, no estoy seguro de qué tan intenso será el rendimiento y requiere poder clonar la clase Connector<T>.

public class Connector<T> : IConnector<T> 
{ 
    // ... 
    private Connector() 
    { 
    } 
    // ... 
    public static explicit operator Connector<IConnectionMaintainable>(Connector<T> other) 
    { 
     Connector<IConnectionMaintainable> connector = null; 

     Type p = typeof(T); 
     if (p.GetInterfaces().Contains(typeof(IConnectionMaintainable))) 
     { 
      connector = new Connector<IConnectionMaintainable>(); 
      connector.channel = other.channel as IConnectionMaintainable; 
     } 
     else 
     { 
      throw new InvalidCastException(); 
     } 

     return connector; 
    } 
} 

public class ConnectorBuilder<T> : IConnectorBuilder<T> 
{ 
    // ... 
    public IConnector<T> GetConnector() 
    { 
     Connector<T> connector = new Connector<T>(endpoint); 

     // If reliability was requested, build the ChannelWatchDog: 
     try 
     { 
      Connector<IConnectionMaintainable> temp = (Connector<IConnectionMaintainable>)connector; 
      ChannelWatchDog<IConnectionMaintainable> watchDog = new ChannelWatchDog<IConnectionMaintainable>(temp); 
     } 
     catch (InvalidCastException) 
     { 
      throw new ArgumentException("Trying to make reliable when not possible"); 
     } 

     return connector; 
    } 
    // ... 
} 
+0

Interesante, voy a probar esto. – joniba

Cuestiones relacionadas