2009-10-02 11 views
10

He definido una clase genérica "Lazy<T>", para la evaluación diferida y el almacenamiento en caché del resultado de un delegado Func<T>.¿Por qué no funciona este uso de moldes implícitos?

que también definen dos operadores de conversión implícitas para que pueda crear un Lazy<T> desde unos Func<T> s, y puedo asignar un Lazy<T> a un T (obtiene el Value del Lazy<T>)

La idea es que se puede pasar alrededor de Lazy<T> en lugar de una instancia de T, pero no hacer el trabajo para calcular/recuperar el valor hasta que se asigne a una instancia real de T.

// class Lazy<T> 
// Encapsulates a value which can be retrieved when first accessed, 
// and is then cached. 
class Lazy<T> 
{ 
    private Func<T> _getter; 
    private T _cached; 
    private bool _isCached; 

    // Get/set the getter delegate 
    // that 'calculates' the value. 
    public Func<T> Getter 
    { 
    get 
    { 
     return _getter; 
    } 
    set 
    { 
     _getter = value; 
     _cached = default(T); 
     _isCached = false; 
    } 
    } 

    // Get/set the value. 
    public T Value 
    { 
    get 
    { 
     if (!_isCached) 
     { 
     _cached = Getter(); 
     _isCached = true; 
     _getter = null; 
     } 
     return _cached; 
    } 
    set 
    { 
     _cached = value; 
     _isCached = true; 
     _getter = null; 
    } 
    } 

    // Implicit casts: 

    // Create a T from a Lazy<T> 
    public static implicit operator T(Lazy<T> lazy) 
    { 
    return lazy.Value; 
    } 

    // Create a Lazy<T> from a Func<T> 
    public static implicit operator Lazy<T>(Func<T> getter) 
    { 
    return new Lazy<T> {Getter = getter}; 
    } 
} 

Pero esta clase no funciona como esperaba en un caso, se destaca en la aplicación de pruebas a continuación:

class Program 
{ 
    static void Main() 
    { 
    // This works okay (1) 
    TestLazy(() => MakeStringList()); 

    // This also works (2) 
    Lazy<string> lazyString = new Func<string>(() => "xyz"); 
    string s = lazyString; 

    //This doesn't compile (3) 
    // 
    Lazy<IList<string>> lazyStrings = new Func<IList<string>>(MakeStringList); 
    IList<string> strings = lazyStrings; //ERROR 
    } 


    static void TestLazy<T>(Func<T> getter) 
    { 
    Lazy<T> lazy = getter; 
    T nonLazy = lazy; 
    } 

    private static IList<string> MakeStringList() 
    { 
    return new List<string> { new string('-', 10) }; 
    } 
} 

En la línea marcada con //ERROR, me sale un error de compilación:

error CS0266: No se puede convertir implícitamente el tipo Lazy<System.Collections.Generic.IList<string>> en System.Collections.Generic.IList<string>. Existe una conversión explícita (¿falta un molde?)

Este error es confuso ya que existe una conversión implícita del origen al tipo de destino en cuestión. Y, a primera vista, el fragmento de código (3) está haciendo lo mismo que (1) Además, difiere de (2) solo por el tipo utilizado para especializar al Lazy.

¿Alguien puede explicarme qué está pasando aquí?

Respuesta

17

El problema es que se Estamos tratando de convertir a IList<T> implícitamente, y IList<T> no está englobado en IList<T> (aunque son del mismo tipo): solo las conversiones a tipos que no sean de interfaz se consideran abarcadoras. De la sección 6.4.3 de la C# 3.0 spec:

If a standard implicit conversion (§6.3.1) exists from a type A to a type B, and if neither A nor B are interface-types, then A is said to be encompassed by B, and B is said to encompass A.

En la sección 6.4.4, hablando de conversiones definidas por el usuario, uno de los pasos es (énfasis mío):

  • Find the set of applicable user-defined and lifted conversion operators, U.

This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in D that convert from a type encompassing S to a type encompassed by T. If U is empty, the conversion is undefined and a compile-time error occurs.

IList<T> ISN' t abarcado por IList<T>, por lo tanto este paso falla.

El compilador hará "encadenado" conversiones implícitas en otros escenarios aunque - por lo que si en realidad tenía una Lazy<List<T>> que podía escritura:

object strings = lazyStrings; 

obras, porque List<T> es abarcado por object (ya que ambos son clases, y hay una conversión implícita estándar de List<T> a object).

Ahora en cuanto a por qué este es el caso, me sospechan que es para detener los casos raros donde se esperaría una conversión de referencia, pero que en realidad obtener la conversión implícita. Supongamos que tenemos:

class ListLazy : Lazy<IList<string>>, IList<string> 
{ 
    // Stuff 
} 
... 
Lazy<IList<string>> x = new ListLazy(); 
IList<string> list = x; 

¿Qué conversión se debe utilizar? Hay una conversión implícita de conversión del tipo real al IList<string> ... pero el compilador no lo sabe porque la expresión es del tipo Lazy<IList<string>>. Básicamente, las interfaces son incómodas porque pueden aparecer más adelante en la jerarquía de tipos, mientras que con una clase siempre sabes dónde estás, si ves lo que quiero decir. (Se prohíben las conversiones implícitas que implican dos clases en la misma jerarquía.)

+0

Lo extraño es que el error de compilación todavía ocurre cuando List se reemplaza por IList (aparte de donde se construye una instancia de List y se devuelve desde un método que devuelve IList. – mackenir

+0

Para ser precisos, ahora el compilador se queja de que no hay implícito emitido desde "Lazy " hasta "T", donde "T == IList ". Aunque fui y edité la pregunta, puede ser que su respuesta aún se mantenga, en cuyo caso tendré que recurrir una investigación qué 'abarca' es para entender la respuesta :) – mackenir

+0

Pero el punto es que todavía estás tratando de convertir a * interfaz * - eso es lo que no le gusta. –

3

¿Podría ser un pequeño error tipográfico?

Lazy<List<string>> lazyStrings = new Func<List<string>>(MakeStringList); 
IList<string> strings = lazyStrings; //ERROR 
List<string> strings = lazyStrings; //OK 

Si hace quieren un IList <>, es una conversión de 2 pasos y supongo que el compilador no quiere salir adelante por sí misma

IList<string> istrings = strings; 
+0

Creo que quiere obtener un IList, y no una lista. – Philippe

+0

Philippe, no es obvio, estaba preguntando. Pero cambia la pregunta a "¿el compilador realiza conversiones implícitas encadenadas" o algo así? –

+0

+1 La conversión implícita requiere que los tipos coincidan exactamente. Si desea un IList , puede cambiar su método/delegado para devolverlo desde el principio o usar un segundo elenco después del lanzamiento implícito a la Lista . – dahlbyk

Cuestiones relacionadas