Como faço para fornecer suporte de elenco personalizado para minha classe?

102

Como fornecer suporte para lançar minha classe para outros tipos? Por exemplo, se eu tiver minha própria implementação de gerenciamento de a byte[]e quiser permitir que as pessoas direcionem minha classe para a byte[], que apenas retornará o membro privado, como faria isso?

É uma prática comum permitir que eles também convertam isso em uma string ou devo apenas substituir ToString()(ou ambos)?

esac
fonte

Respostas:

112

Você precisaria substituir o operador de conversão, usando implicitou explicitdependendo se deseja que os usuários tenham que lançá-lo ou se você deseja que aconteça de forma automática e mágica. Geralmente, uma direção sempre funcionará, é onde você usa implicit, e a outra direção às vezes pode falhar, é onde você usa explicit.

A sintaxe é assim:

public static implicit operator dbInt64(Byte x)
{
    return new dbInt64(x);
}

ou

public static explicit operator Int64(dbInt64 x)
{
    if (!x.defined)
        throw new DataValueNullException();
    return x.iVal;
}

Para seu exemplo, digamos do seu tipo personalizado ( MyType-> byte[]sempre funcionará):

public static implicit operator byte[] (MyType x)
{
    byte[] ba = // put code here to convert x into a byte[]
    return ba;
}

ou

public static explicit operator MyType(byte[] x)
{
    if (!CanConvert)
        throw new DataValueNullException();

    // Factory to convert byte[] x into MyType
    MyType mt = MyType.Factory(x);
    return mt;
}
Charles Bretana
fonte
36

Você pode declarar operadores de conversão em sua classe usando as palavras-chave explicitou implicit.

Como regra geral, você só deve fornecer implicitoperadores de conversão quando a conversão não puder falhar. Use explicitoperadores de conversão quando a conversão pode falhar.

public class MyClass
{
    private byte[] _bytes;

    // change explicit to implicit depending on what you need
    public static explicit operator MyClass(byte[] b)
    {
        MyClass m = new MyClass();
        m._bytes = b;
        return m;
    }

    // change explicit to implicit depending on what you need
    public static explicit operator byte[](MyClass m)
    {
        return m._bytes;
    }
}

Usar explicitsignifica que os usuários de sua classe precisarão fazer uma conversão explícita:

byte[] foo = new byte[] { 1, 2, 3, 4, 5 };
// explicitly convert foo into an instance of MyClass...
MyClass bar = (MyClass)foo;
// explicitly convert bar into a new byte[] array...
byte[] baz = (byte[])bar;

Usar implicitsignifica que os usuários de sua classe não precisam realizar uma conversão explícita, tudo acontece de forma transparente:

byte[] foo = new byte[] { 1, 2, 3, 4, 5 };
// imlpicitly convert foo into an instance of MyClass...
MyClass bar = foo;
// implicitly convert bar into a new byte[] array...
byte[] baz = bar;
LukeH
fonte
6

Eu prefiro ter algum método que faça isso em vez de sobrecarregar o operador de elenco.

Veja c # explícito e implícito, mas observe que a partir desse exemplo, usando o método explícito, se você:

string name = "Test";
Role role = (Role) name;

Então está tudo bem; no entanto, se você usar:

object name = "Test";
Role role = (Role) name;

Você agora obterá uma InvalidCastException porque a string não pode ser convertida para Role, ora, o compilador procura apenas conversões implícitas / explícitas em tempo de compilação com base em seu tipo compilado. Nesse caso, o compilador vê o nome como um objeto em vez de string e, portanto, não usa o operador sobrecarregado de Role.

Chris Chilvers
fonte
Olhando para o exemplo que você vinculou, parece criar uma nova instância do objeto em cada elenco. Alguma ideia de como apenas obter / definir o tipo de operações em um membro atual da classe?
esac
3

Para suporte de conversão customizada, você precisa fornecer operadores de conversão (explícitos ou implícitos). O exemplo a seguir da classe EncodedString é uma implementação simplista de string com codificação personalizada (pode ser útil se você tiver que processar strings enormes e enfrentar problemas de consumo de memória porque as strings .Net são Unicode - cada caractere ocupa 2 bytes de memória - e EncodedString pode levar 1 byte por caractere).

EncodedString pode ser convertido em byte [] e em System.String. Comentários no código lançam alguma luz e também explicam um exemplo de quando a conversão implícita pode ser perigosa.

Normalmente, você precisa de um bom motivo para declarar quaisquer operadores de conversão em primeiro lugar, porque.

Leituras adicionais estão disponíveis no MSDN .

class Program
{
    class EncodedString
    {
        readonly byte[] _data;
        public readonly Encoding Encoding;

        public EncodedString(byte[] data, Encoding encoding)
        {
            _data = data;
            Encoding = encoding;
        }

        public static EncodedString FromString(string str, Encoding encoding)
        {
            return new EncodedString(encoding.GetBytes(str), encoding);
        }

        // Will make assumption about encoding - should be marked as explicit (in fact, I wouldn't recommend having this conversion at all!)
        public static explicit operator EncodedString(byte[] data)
        {
            return new EncodedString(data, Encoding.Default);
        }

        // Enough information for conversion - can make it implicit
        public static implicit operator byte[](EncodedString obj)
        {
            return obj._data;
        }

        // Strings in .Net are unicode so we make no assumptions here - implicit
        public static implicit operator EncodedString(string text)
        {
            var encoding = Encoding.Unicode;
            return new EncodedString(encoding.GetBytes(text), encoding);
        }

        // We have all the information for conversion here - implicit is OK
        public static implicit operator string(EncodedString obj)
        {
            return obj.Encoding.GetString(obj._data);
        }
    }

    static void Print(EncodedString format, params object[] args)
    {
        // Implicit conversion EncodedString --> string
        Console.WriteLine(format, args);
    }

    static void Main(string[] args)
    {
        // Text containing russian letters - needs care with Encoding!
        var text = "Привет, {0}!";

        // Implicit conversion string --> EncodedString
        Print(text, "world");

        // Create EncodedString from System.String but use UTF8 which takes 1 byte per char for simple English text
        var encodedStr = EncodedString.FromString(text, Encoding.UTF8);
        var fileName = Path.GetTempFileName();

        // Implicit conversion EncodedString --> byte[]
        File.WriteAllBytes(fileName, encodedStr);

        // Explicit conversion byte[] --> EncodedString
        // Prints *wrong* text because default encoding in conversion does not match actual encoding of the string
        // That's the reason I don't recommend to have this conversion!
        Print((EncodedString)File.ReadAllBytes(fileName), "StackOverflow.com");

        // Not a conversion at all. EncodingString is instantiated explicitly
        // Prints *correct* text because encoding is specified explicitly
        Print(new EncodedString(File.ReadAllBytes(fileName), Encoding.UTF8), "StackOverflow.com");

        Console.WriteLine("Press ENTER to finish");
        Console.ReadLine();
    }
}
Konstantin Spirin
fonte