Jak mogę zapewnić obsługę niestandardowych rzutów dla mojej klasy?

103

Jak mogę zapewnić obsługę rzutowania mojej klasy na inne typy? Na przykład, jeśli mam własną implementację zarządzania a byte[]i chcę, aby ludzie rzucali moją klasę na a byte[], co po prostu zwróci członka prywatnego, jak mam to zrobić?

Czy powszechną praktyką jest pozwalanie im również rzutować to na strunę, czy powinienem po prostu przesłonić ToString()(lub oba)?

esac
źródło

Odpowiedzi:

114

Musiałbyś zastąpić operator konwersji, używając jednego implicitlub w explicitzależności od tego, czy chcesz, aby użytkownicy musieli go przesyłać, czy też chcesz, aby działo się to automagicznie. Ogólnie rzecz biorąc, jeden kierunek zawsze będzie działał, tam właśnie używasz implicit, a drugi kierunek może czasami zawieść, właśnie tam używasz explicit.

Składnia jest następująca:

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

lub

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

Na przykład powiedz z własnego typu ( MyType-> byte[]będzie zawsze działać):

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

lub

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
źródło
36

Możesz zadeklarować operatory konwersji w swojej klasie, używając słów kluczowych explicitlub implicit.

Zgodnie z ogólną zasadą implicitoperatory konwersji należy podawać tylko wtedy, gdy konwersja nie może się nie powieść. Użyj explicitoperatorów konwersji, gdy konwersja może się nie powieść.

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

Użycie explicitoznacza, że ​​użytkownicy Twojej klasy będą musieli dokonać jawnej konwersji:

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;

Używanie implicitoznacza, że ​​użytkownicy Twojej klasy nie muszą dokonywać jawnej konwersji, wszystko dzieje się w sposób przejrzysty:

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
źródło
6

Wolę mieć jakąś metodę, która to zrobi, zamiast przeciążać operatora rzutowania.

Zobacz jawne i niejawne c #, ale zwróć uwagę, że z tego przykładu przy użyciu metody jawnej, jeśli to zrobisz:

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

Wtedy wszystko jest w porządku; jednak jeśli używasz:

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

Otrzymasz teraz InvalidCastException, ponieważ ciąg nie może być rzutowany na rolę, dlaczego kompilator szuka tylko niejawnych / jawnych rzutów w czasie kompilacji na podstawie ich skompilowanego typu. W tym przypadku kompilator widzi nazwę jako obiekt, a nie jako ciąg znaków, a zatem nie używa przeciążonego operatora roli.

Chris Chilvers
źródło
Patrząc na przykład, do którego tworzyłeś łącze, wydaje się, że tworzy on nową instancję obiektu przy każdym rzutowaniu. Masz jakiś pomysł, jak po prostu pobrać / ustawić typ operacji na bieżącym elemencie klasy?
esac
3

Aby obsługiwać niestandardowe rzutowanie, musisz podać operatory rzutowania (jawne lub niejawne). Poniższy przykład klasy EncodedString jest uproszczoną implementacją ciągu znaków z niestandardowym kodowaniem (może być przydatny, jeśli musisz przetwarzać ogromne, ogromne ciągi i napotkasz problemy z zużyciem pamięci, ponieważ łańcuchy sieciowe są Unicode - każdy znak zajmuje 2 bajty pamięci - i EncodedString może zająć 1 bajt na znak).

EncodedString można przekonwertować na bajt [] i na System.String. Komentarze w kodzie rzucają trochę światła, a także wyjaśniają przykład, kiedy niejawna konwersja może być niebezpieczna.

Zwykle potrzebujesz bardzo dobrego powodu, aby zadeklarować jakiekolwiek operatory konwersji w pierwszej kolejności, ponieważ.

Więcej informacji można znaleźć w witrynie 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
źródło