Bagaimana cara memberikan dukungan cast kustom untuk kelas saya?

103

Bagaimana cara memberikan dukungan untuk mentransmisikan kelas saya ke jenis lain? Misalnya, jika saya memiliki implementasi saya sendiri untuk mengelola a byte[], dan saya ingin mengizinkan orang mentransmisikan kelas saya ke a byte[], yang hanya akan mengembalikan anggota privat, bagaimana cara melakukannya?

Apakah praktik umum untuk membiarkan mereka juga mentransmisikan ini ke string, atau haruskah saya menimpa ToString()(atau keduanya)?

esac
sumber

Jawaban:

113

Anda perlu mengganti operator konversi, menggunakan salah satu implicitatau explicitbergantung pada apakah Anda ingin pengguna mentransmisikannya atau Anda ingin ini terjadi secara otomatis. Umumnya, satu arah akan selalu berhasil, di situlah Anda menggunakan implicit, dan arah lainnya terkadang gagal, di situlah Anda menggunakan explicit.

Sintaksnya seperti ini:

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

atau

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

Untuk contoh Anda, katakan dari Jenis kustom Anda ( MyType-> byte[]akan selalu berfungsi):

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

atau

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
sumber
36

Anda dapat mendeklarasikan operator konversi di kelas Anda menggunakan kata kunci explicitatau implicit.

Sebagai pedoman umum, Anda hanya boleh menyediakan implicitoperator konversi jika konversi tidak mungkin gagal. Gunakan explicitoperator konversi saat konversi mungkin gagal.

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

Menggunakan explicitberarti bahwa pengguna kelas Anda perlu melakukan konversi eksplisit:

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;

Menggunakan implicitberarti bahwa pengguna kelas Anda tidak perlu melakukan konversi eksplisit, semuanya terjadi secara transparan:

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
sumber
6

Saya lebih suka memiliki beberapa metode yang akan melakukannya daripada membebani operator cor.

Lihat c # eksplisit dan implisit tetapi perhatikan bahwa dari contoh itu, menggunakan metode eksplisit, jika Anda melakukannya:

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

Maka semuanya baik-baik saja; Namun, jika Anda menggunakan:

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

Anda sekarang akan mendapatkan InvalidCastException karena string tidak dapat ditransmisikan ke Role, mengapa, kompilator hanya mencari cast implisit / eksplisit pada waktu kompilasi berdasarkan jenis kompilasi mereka. Dalam hal ini kompilator melihat nama sebagai objek daripada string, dan karenanya tidak menggunakan operator Role yang kelebihan beban.

Chris Chilvers
sumber
Melihat contoh yang Anda tautkan, tampaknya membuat instance baru dari objek di setiap cast. Ada ide bagaimana melakukan operasi get / set pada anggota kelas saat ini?
esac
3

Untuk dukungan cast kustom, Anda perlu menyediakan operator cast (eksplisit atau implisit). Contoh kelas EncodedString berikut adalah implementasi sederhana dari string dengan pengkodean khusus (mungkin berguna jika Anda harus memproses string yang sangat besar dan mengalami masalah konsumsi memori karena. String bersih adalah Unicode - setiap karakter membutuhkan 2 byte memori - dan EncodedString dapat mengambil 1 byte per karakter).

EncodedString dapat diubah menjadi byte [] dan ke System.String. Komentar dalam kode menjelaskan beberapa hal dan juga menjelaskan contoh ketika konversi implisit bisa berbahaya.

Biasanya Anda memerlukan alasan yang sangat bagus untuk mendeklarasikan setiap operator konversi karena.

Bacaan lebih lanjut tersedia di 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
sumber