Conversión implícita con un tipo genérico que falla para un tipo de interfaz

Sep 11 2020
        private struct Maybe<T>
        {
            private readonly T value;
            private readonly bool hasValue;

            private Maybe(T value)
            {
                this.value = value;
                hasValue = true;
            }

            public static implicit operator Maybe<T>(T value) =>
                value == null ? new Maybe<T>() : new Maybe<T>(value);
        }

        private static Maybe<byte> OK()
        {
            return 5;
        }

        private static Maybe<IEnumerable<byte>> NotOK()
        {
            var e = new[] { 1, 2, 3 }.Select(x => (byte)x);
            Console.WriteLine(e.GetType().Name);
            return e;
        }

Violín (no usar): https://dotnetfiddle.net/NxAw9l

Violín actualizado: https://dotnetfiddle.net/NrARTl

Algún tipo genérico no logra la conversión implícita en el código anterior. Ver Ok()y NotOk()llamadas de función y tipos de devolución. Un tipo genérico complejo está fallando y no entiendo por qué. He simplificado esto a partir de una función de un tipo de retorno de IEnumerable<IEnumerable<T>>. Esto IEnumerable<T>todavía falla. Creo que si puedo entender por qué esto falla, supongo que también resolvería el verdadero. Gracias por su ayuda y tiempo.

Aquí está el mensaje de error si lo desea:

Error    CS0029    Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<byte>' to 'Maybe<System.Collections.Generic.IEnumerable<byte>>'

Actualización: Devolver Byte [] de NotOK () no puede funcionar porque en mi código fuente real tengo una consulta LINQ Tengo que depender de su ejecución diferida diferida (es decir, tiene que devolver estrictamente IEnumerable) (ver respuesta similar =>https://stackoverflow.com/a/63880804/5917087).

Respuestas

5 Heinzi Sep 14 2020 at 15:46

Actualmente, el estándar C # no permite conversiones implícitas desde o hacia interfaces.

Este es un problema bien conocido al implementar un tipo Maybe<T>(o Optional<T>, como se le suele llamar) en C #. Hay una discusión en curso sobre esto en el foro de github del lenguaje C #:

  • https://github.com/dotnet/roslyn/issues/14186

Como solución alternativa, puede crear el Maybe<T>constructor internaly agregar una clase auxiliar estática no genérica:

private static class Maybe
{
    public static Maybe<T> From<T>(T value) => 
        value == null ? new Maybe<T>() : new Maybe<T>(value);
}

que le permite utilizar la inferencia de tipos y la escritura Maybe.From(a), que es un poco más corta que new Maybe<IEnumerable<byte>>(a).

2 weichch Sep 15 2020 at 07:10

Voy a extender la respuesta de @Heinzi:

También puede utilizar métodos de extensión:

static class MaybeExtensions
{
    public static Maybe<T> AsMaybe<T>(this T value)
    {
        return new Maybe<T>(value);
    }

    public static Maybe<TResult> AsMaybe<T, TResult>(this T value)
        where T : unmanaged
        where TResult : unmanaged
    {
        return new Maybe<TResult>(Unsafe.As<T, TResult>(ref value));
    }
}

Y en sus métodos de llamada, puede usarlos como:

private static Maybe<IEnumerable<byte>> NotOK()
{
    var e = new[] { 1, 2, 3 }.Select(x => (byte)x);
    return e.AsMaybe();
}

private static Maybe<byte> OK()
{
    return 5.AsMaybe<int, byte>();
}

// Alternatively
private static Maybe<byte> OK()
{
    return ((byte)5).AsMaybe();
}

Necesita la AsMaybe<T, TResult>sobrecarga para los tipos de valor que se pueden convertir entre sí. Por ejemplo cuando se hace 5.AsMaybe()devuelve Maybe<int>, si el tipo de devolución de su método es Maybe<byte>que se necesita para convertir Maybe<int>a Maybe<byte>, y la sobrecarga hace por usted.

Ahora, el operador de conversión de tipos en se Maybe<T>vuelve redundante. Y puede usar en varlugar del nombre de tipo completo:

Maybe<int> obj1 = 5; // use operator
var obj2 = 5.AsMaybe(); // use extension method
Sam Sep 11 2020 at 22:24

No puede definir una conversión a / desde un tipo de interfaz, si cambia su ejemplo para usar en List<T>lugar de IEnumerable<T>compilar:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/conversions#user-defined-conversions

AnGG Sep 14 2020 at 15:34

Cambia esto :

    private static Maybe<IEnumerable<byte>> NotOK()
    {
        IEnumerable<byte> a = new byte[] { 1, 2 };
        return a;
    }

dentro de esto :

    private static Maybe<IEnumerable<byte>> NotOK()
    {
        var a = new byte[] { 1, 2 };
        return a;
    }

La estructura:

private struct Maybe<T>
{
    private readonly T value;
    private readonly bool hasValue;

    private Maybe(T value)
    {
        this.value = value;
        hasValue = true;
    }

    public static implicit operator Maybe<T>(T value)
    {
        return value == null ? new Maybe<T>() : new Maybe<T>(value);
    }
    
    public bool HasValue(){
        return this.hasValue;   
    }
    
    public T GetValue(){
        return this.value;  
    }
    
}
private static Maybe<byte> OK()
{
    return 5;
}
private static Maybe<IEnumerable<byte>> NotOK()
{
    Byte[] a = new byte[] { 1, 2 };
    Console.WriteLine(a.GetType().Name);
    return a;
}

Uso:

public static void Main(string[] args){
        
    var t1 = OK();
    var t2 = NotOK();
    
    Console.WriteLine("t1 type is "  + t1.GetType().Name);
    Console.WriteLine("t2 type is "  + t2.GetType().Name);
    
    if(t2.HasValue())
    {
        List<byte> search = t2.GetValue().Where(b => b > 0).ToList();
        foreach(byte num in search){
            Console.WriteLine(num); 
        }
    }
}

La referencia IEnumerable<byte> ano cambia el tipo, puede continuar con varo byte[]y la consulta con LINQ, después, ver en el ejemplo completo

Vea el ejemplo completo: https://dotnetfiddle.net/V8RHQe

BahtiyarÖzdere Sep 17 2020 at 23:39

IEnumerable es una interfaz. El compilador no sabe con qué tipo trabajar. Ponga ToList()al final de su selección como sigue:

private static Maybe<IEnumerable<byte>> NotOK()
{
        var e = new[] { 1, 2, 3 }.Select(x => (byte)x).ToList();
        Console.WriteLine(e.GetType().Name);
        return e;
}

Para comprender lo que está sucediendo, intente crear un método como seguir en su clase y ver el compilador llorar :)

public static implicit operator Maybe<IEnumerable<T>>(IEnumerable<T> value)
{
        return value == null ? new Maybe<IEnumerable<T>>() : new Maybe<IEnumerable<T>>(value);
}