2010-03-04 14 views
6

Supongamos que escribo una biblioteca con lo siguiente:¿Es posible especificar una restricción genérica para que un parámetro de tipo sea convertible DE OTRO tipo?

public class Bar { /* ... */ } 

public class SomeWeirdClass<T> 
    where T : ??? 
{ 
    public T BarMaker(Bar b) 
    { 
     // ... play with b 
     T t = (T)b 
     return (T) b; 
    } 
} 

Más tarde, espero que los usuarios utilizar mi biblioteca mediante la definición de sus propios tipos que son convertibles a Bar y utilizando el SomeWeirdClass 'fábrica'.

public class Foo 
{ 
    public static explicit operator Foo(Bar f) 
    { 
     return new Bar(); 
    } 
} 

public class Demo 
{ 
    public static void demo() 
    { 
     Bar b = new Bar(); 
     SomeWeirdClass<Foo> weird = new SomeWeirdClass<Foo>(); 
     Foo f = weird.BarMaker(b); 
    } 
} 

este compilará si fijo where T : Foo pero el problema es que yo no sé nada de Foo en tiempo de compilación de la biblioteca, y en realidad quiero algo más como where T : some class that can be instantiated, given a Bar

Es esto posible? De mi limitado conocimiento, no parece ser así, pero el ingenio de .NET Framework y sus usuarios siempre me sorprende ...

Esto puede o no estar relacionado con la idea de static interface methods - al menos, puedo ver el valor de ser capaz de especificar la presencia de métodos de fábrica para crear objetos (similar a la de la misma manera que ya se puede realizar where T : new())

edición: Solución - gracias a Nick y bzIm - para otros lectores voy proporcionar una solución completa según la entiendo: edición2: esta solución requiere que Foo exponga un constructor predeterminado público. Para una solución incluso stupider mejor que no requiere esto, vea la parte inferior de esta publicación.

public class Bar {} 

public class SomeWeirdClass<T> 
    where T : IConvertibleFromBar<T>, new() 
{ 
    public T BarMaker(Bar b) 
    { 
     T t = new T(); 
     t.Convert(b); 
     return t; 
    } 
} 

public interface IConvertibleFromBar<T> 
{ 
    T Convert(Bar b); 
} 

public class Foo : IConvertibleFromBar<Foo> 
{ 
    public static explicit operator Foo(Bar f) 
    { 
     return null; 
    } 

    public Foo Convert(Bar b) 
    { 
     return (Foo) b; 
    } 
} 

public class Demo 
{ 
    public static void demo() 
    { 
     Bar b = new Bar(); 
     SomeWeirdClass<Foo> weird = new SomeWeirdClass<Foo>(); 
     Foo f = weird.BarMaker(b); 
    } 
} 

Edit2: Solución 2: Crear una fábrica de convertidor Tipo de empleo:

#region library defined code 

public class Bar {} 

public class SomeWeirdClass<T, TFactory> 
    where TFactory : IConvertorFactory<Bar, T>, new() 
{ 
    private static TFactory convertor = new TFactory(); 

    public T BarMaker(Bar b) 
    { 
     return convertor.Convert(b); 
    } 
} 

public interface IConvertorFactory<TFrom, TTo> 
{ 
    TTo Convert(TFrom from); 
} 

#endregion 

#region user defined code 

public class BarToFooConvertor : IConvertorFactory<Bar, Foo> 
{ 
    public Foo Convert(Bar from) 
    { 
     return (Foo) from; 
    } 
} 

public class Foo 
{ 
    public Foo(int a) {} 

    public static explicit operator Foo(Bar f) 
    { 
     return null; 
    } 

    public Foo Convert(Bar b) 
    { 
     return (Foo) b; 
    } 
} 

#endregion 

public class Demo 
{ 
    public static void demo() 
    { 
     Bar b = new Bar(); 
     SomeWeirdClass<Foo, BarToFooConvertor> weird = new SomeWeirdClass<Foo, BarToFooConvertor>(); 
     Foo f = weird.BarMaker(b); 
    } 
} 
+3

¿No es el objetivo de crear API para que sean * fáciles * de usar? –

+1

Considérelo una pregunta pedagógica interesante. – fostandy

Respuesta

4

No creo que haya necesariamente una forma sintácticamente fresco para hacer esto integrado en el lenguaje. Una posible solución a su problema podría ser la definición de una interfaz convertibles:

public interface IConvertible<T> 
    where T : new() // Probably will need this 
{ 
    T Convert(); 
} 

Entonces su clase podrían ser:

public class Foo : IConvertible<Bar> 
{ 
} 

Creo que esto le acerca a la que desea ser ... Todo los Foo's y Bar's en su pregunta a veces dificultan determinar exactamente cuál es su intención. Espero que esto ayude.

Editar: Agregado a la restricción ... probablemente tendrá que ser capaz de crear una nueva instancia en su clase convertible.

Edición 2: Hecho Foo hereda de ICovertible<Bar>

+0

Según tengo entendido, defino un Foo: IConvertible ? Eso me permitirá convertir instancias de Foo a instancias de Bar, pero en realidad quiero convertir instancias de Bar a instancias de Foo. Creo que realmente necesitaría implementar Bar: IConvertible - pero, por supuesto, no conociendo a Foo en tiempo de compilación de la biblioteca no puedo hacer esto. – fostandy

+0

Y mis disculpas por la abstracción. Trato de evitar eso donde sea posible, pero la aplicación específica de esto es algo intrincada en sí misma. – fostandy

+0

@fostandy - Para ser sincero, estoy teniendo dificultades para entender exactamente lo que estás tratando de hacer, especialmente con los Foos, Bars, y diciendo que no conoces a Foo en el momento de la compilación de la biblioteca. ¿Puedes dejar de ofuscar tu pregunta un poco? – Nick

1

Se podría hacer un desvío a través de una interfaz que se utiliza como una restricción de tipo.

Por ejemplo, where T : IComparable<U> se utiliza para restringir el tipo a algo que se puede comparar con otra cosa, que debe expresar esta capacidad implementando IComparable<another>. Si tenía una interfaz ICastableFrom<T>, puede lograr lo que desea al forzarlos a implementar ICastableFrom<Bar>.

4

Parece que ha encontrado una solución al problema más grande.Para responder a su pregunta específica: no, ni C# ni CLR admiten la restricción de parámetro de tipo genérico "hacia atrás". Es decir,

class C<T> where Foo : T 

"T debe ser Foo o un tipo que Foo convierte a" no es compatible.

Hay idiomas que tienen ese tipo de restricción; IIRC Scala es ese lenguaje. Sospecho que esta característica sería útil para ciertos usos de interfaces contravariantes.

1

En lugar de tomarse la molestia de definir una interfaz y modificar su clase para implementar esa interfaz, ¿por qué no hacer esto?

public class SomeWeirdClass<T> 
{ 
    // aside: why is this method called 'BarMaker' if it returns a T? 
    public T BarMaker(Bar b, Func<Bar, T> converter) 
    { 
     // ... play with b 
     return converter(b); 
    } 
} 

Luego, en el caso de que se trata de un objeto de un tipo T a la que Bar pueda emitir directamente, este método podría ser llamado simplemente como sigue:

var someWeirdObject = new SomeWeirdClass<Foo>(); 
var someBar = new Bar(); 
var someFoo = someWeirdObjcet.BarMaker(someBar, bar => bar as Foo); 

Por cierto (dado que el delegado Func<T, TResult> apareció en .NET 3.5), también puede usar Converter<TInput, TOutput> (que es exactamente el mismo) para el parámetro converter.

+0

Excelente punto. Supongo que me obsesioné tanto con los genéricos y las limitaciones que me extrañé por completo. – fostandy

Cuestiones relacionadas