Conversão implícita com falha de tipo genérico para um tipo de interface

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;
        }

Fiddle (não use): https://dotnetfiddle.net/NxAw9l

Violino atualizado: https://dotnetfiddle.net/NrARTl

Algum tipo genérico está falhando para conversão implícita no código acima. Veja as Ok()e NotOk()chamadas de função e tipos de retorno. Um tipo genérico complexo está falhando e não entendo por quê. Eu simplifiquei isso de uma função de um tipo de retorno de IEnumerable<IEnumerable<T>>. Isso IEnumerable<T>ainda falha. Acho que, se eu conseguir entender por que isso falha, também resolveria o verdadeiro, suponho. Obrigado por sua ajuda e tempo.

Esta é a mensagem de erro, se desejar:

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

Atualização: Retornar Byte [] de NotOK () não pode funcionar porque em meu código-fonte real eu tenho uma consulta LINQ que tenho que depender de sua execução preguiçosa adiada (ou seja, deve estar retornando estritamente IEnumerable) (ver resposta semelhante =>https://stackoverflow.com/a/63880804/5917087)

Respostas

5 Heinzi Sep 14 2020 at 15:46

O padrão C # atualmente não permite conversões implícitas de ou para interfaces.

Este é um problema bem conhecido ao implementar um tipo Maybe<T>(ou Optional<T>, como é freqüentemente chamado) em C #. Há uma discussão em andamento sobre isso no fórum do github em linguagem C #:

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

Como alternativa, você pode criar o Maybe<T>construtor internale adicionar uma classe auxiliar não genérica estática:

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

que permite usar inferência de tipo e gravação Maybe.From(a), que é um pouco menor que new Maybe<IEnumerable<byte>>(a).

2 weichch Sep 15 2020 at 07:10

Vou estender a resposta de @Heinzi:

Você também pode usar métodos de extensão:

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));
    }
}

E em seus métodos de chamada, você pode usá-los 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();
}

Você precisa da AsMaybe<T, TResult>sobrecarga para tipos de valor que podem ser convertidos entre si. Por exemplo, quando você faz 5.AsMaybe()isso retorna Maybe<int>, se o tipo de retorno do seu método é, Maybe<byte>você precisará converter Maybe<int>para Maybe<byte>, e a sobrecarga faz isso por você.

Agora, o operador de conversão de tipo em Maybe<T>se torna redundante. E você pode usar em varvez do nome do tipo completo:

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

Você não pode definir uma conversão de / para um tipo de interface, se você alterar seu exemplo para usar em List<T>vez 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

Mude isso :

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

nisso :

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

A estrutura:

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); 
        }
    }
}

A referência IEnumerable<byte> anão muda o tipo, você pode continuar com varou byte[]e a consulta com LINQ, depois, veja no exemplo completo

Veja o exemplo completo: https://dotnetfiddle.net/V8RHQe

BahtiyarÖzdere Sep 17 2020 at 23:39

IEnumerable é uma interface. O compilador não sabe com qual tipo trabalhar. Conclua ToList()sua seleção da seguinte maneira:

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 entender o que está acontecendo, tente criar um método como seguir em sua aula e assistir compilador chorar :)

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