2010-08-31 18 views
36

¿No es compatible, es compatible pero tengo que hacer algunos trucos?¿Es compatible el constructor genérico en clase no genérica?

Ejemplo:

class Foo 
{ 
    public Foo<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2) 
    { 
    ... 
    } 
} 

los genéricos sólo se utilizan en el constructor, no hay ningún campo/propiedad dependía de ellos, lo uso (genéricos) para hacer cumplir la correlación tipo para f1 y f2.

Observación: Encontré la solución - método estático Crear, pero de todos modos tengo curiosidad por saber por qué tengo problemas con el enfoque directo.

Respuesta

58

No, los constructores genéricos no se admiten en clases genéricas ni genéricas. Del mismo modo, los eventos genéricos, propiedades y finalizadores no son compatibles.

Solo de vez en cuando acepto que sería útil, pero la sintaxis se vería bastante horrible. Por ejemplo, supongamos que tiene:

public class Foo<T> {} 

public class Foo 
{ 
    public Foo<T>() {} 
} 

lo que haría

new Foo<string>() 

? ¿Llamar al constructor genérico de la clase no genérica, o al constructor normal de la clase genérica? Habría que diferenciar entre ellos de alguna manera, y sería complicado :(

Del mismo modo, considere un constructor genérica en una clase genérica:?

public class Foo<TClass> 
{ 
    public Foo<TConstructor>() {} 
} 

¿Cómo se llama al constructor de esperar todo lo posible de acuerdo en que:

new Foo<string><int>() 

es bastante horrible ...

Así que sí, semánticamente sería útil en ocasiones - pero la fealdad resultante contrarresta que, Desafortunadamente.

+0

Podría evitar el problema de clase del mismo nombre al no permitir una clase genérica y no genérica con el mismo nombre (en serio, ¿C# permite esto?). Para el constructor genérico de la clase genérica, no creo que sea demasiado horrible, solo es genérico de mayor orden. –

+2

Una observación: el constructor de la clase genérica es genérico porque la clase es genérica. Sin embargo (tomando su respuesta) no puede especificar ** argumentos ** genéricos adicionales. ¡Gracias por muy buenos ejemplos! – greenoldman

+1

@Peter: No, el problema de clase con nombre samed no es un problema, porque aunque * puede * "sobrecargar" clases por tipo de letra, no hay ambigüedad. Ver 'Tuple' para un ejemplo de eso. –

15

constructores genéricos no son compatibles, pero se puede evitar esto simplemente definiendo un genérico static método, que devuelve un nuevo Foo:

class Foo 
{ 
    public static Foo CreateFromFuncs<T1,T2>(Func<T1,T2> f1,Func<T2,T1> f2) 
    { 
    ... 
    } 
} 

que se utiliza como esto:

// create generic dependencies 
var func1 = new Func<byte, string>(...); 
var func2 = new Func<string, byte>(...); 

// create nongeneric Foo from dependencies 
Foo myFoo = Foo.CreateFromFuncs<byte, string>(func1, func2); 
0

Aquí hay un ejemplo práctico sobre cómo le gustaría tener un parámetro de tipo de constructor adicional y la solución alternativa.

voy a introducir un simple envoltorio para RefCountedIDisposable:

public class RefCounted<T> where T : IDisposable 
{ 
    public RefCounted(T value) 
    { 
     innerValue = value; 
     refCount = 1; 
    } 

    public void AddRef() 
    { 
     Interlocked.Increment(ref refCount); 
    } 

    public void Dispose() 
    { 
     if(InterlockedDecrement(ref refCount)<=0) 
      innerValue.Dispose(); 
    } 

    private int refCount; 
    private readonly innerValue; 
} 

Esto parece estar bien. Pero tarde o temprano le gustaría lanzar un RefCounted<Control> a RefCounted<Button> mientras mantiene el recuento de referencias de objeto, es decir, solo cuando ambas instancias están dispuestas a deshacerse del objeto subyacente.

La mejor manera es si se puede escribir (como las personas C++ pueden hacer)

public RefCounted(RefCounted<U> other) 
{ 
    ...whatever... 
} 

Pero C# no permite esto. Entonces la solución es usar algo indirecto.

private readonly Func<T> valueProvider; 
private readonly Action disposer; 

private RefCounted(Func<T> value_provider, Action disposer) 
{ 
    this.valueProvider = value_provider; 
    this.disposer = disposer; 
} 

public RefCounted(T value) : this(() => value, value.Dispose) 
{ 
} 

public RefCounted<U> Cast<U>() where U : T 
{ 
    AddRef(); 
    return new RefCounted<U>(() => (U)(valueProvider()),this.Dispose); 
} 

public void Dispose(){ 
    if(InterlockedDecrement(ref refCount)<=0) 
     disposer(); 
} 

Si su clase tiene campos que son de tipo genérico, no tiene más remedio que poner todos esos tipos a la clase. Sin embargo, si solo quieres ocultar algún tipo del constructor, necesitarás usar el truco anterior: tener un constructor oculto para juntar todo y definir una función genérica normal para llamar a ese constructor.

Cuestiones relacionadas