Как обеспечить поддержку кастомного приведения для моего класса?

103

Как обеспечить поддержку приведения моего класса к другим типам? Например, если у меня есть собственная реализация управления a byte[], и я хочу, чтобы люди приводили мой класс к a byte[], который просто возвращает закрытый член, как бы я это сделал?

Это обычная практика, позволяющая им также преобразовывать это в строку, или я должен просто переопределить ToString()(или и то, и другое)?

esac
источник

Ответы:

114

Вам нужно будет переопределить оператор преобразования, используя любой из них implicitили в explicitзависимости от того, хотите ли вы, чтобы пользователи выполняли его преобразование или вы хотите, чтобы это происходило автоматически. Как правило, одно направление всегда будет работать, это то, что вы используете implicit, а другое направление может иногда не работать, вот где вы используете explicit.

Синтаксис такой:

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

или

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

В вашем примере, скажем, из вашего пользовательского типа ( MyType-> byte[]всегда будет работать):

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

или

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;
}
Чарльз Бретана
источник
36

Вы можете объявить операторы преобразования в своем классе с помощью ключевых слов explicitили implicit.

Как правило, вы должны предоставлять implicitоператоры преобразования только в том случае, если преобразование невозможно. Используйте explicitоператоры преобразования, если преобразование может завершиться ошибкой.

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

Использование explicitозначает, что пользователям вашего класса нужно будет выполнить явное преобразование:

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;

Использование implicitозначает, что пользователям вашего класса не нужно выполнять явное преобразование, все происходит прозрачно:

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;
Лука
источник
6

Я предпочитаю иметь какой-нибудь метод, который будет делать это, а не перегружать оператор приведения.

См. Явный и неявный C #, но обратите внимание, что из этого примера, используя явный метод, если вы это сделаете:

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

Тогда все хорошо; однако, если вы используете:

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

Теперь вы получите InvalidCastException, потому что строка не может быть приведена к роли, почему компилятор ищет только неявные / явные приведения во время компиляции на основе их скомпилированного типа. В этом случае компилятор видит имя как объект, а не строку, и поэтому не использует перегруженный оператор роли.

Крис Чилверс
источник
Глядя на пример, с которым вы связались, кажется, что при каждом приведении создается новый экземпляр объекта. Есть идеи, как просто выполнять операции типа get / set для текущего члена класса?
esac,
3

Для поддержки кастомного приведения вам необходимо предоставить операторы приведения (явные или неявные). Следующий пример класса EncodedString представляет собой упрощенную реализацию строки с настраиваемой кодировкой (может быть полезно, если вам нужно обрабатывать огромные-огромные строки и сталкиваться с проблемами потребления памяти, потому что строки .Net являются Unicode - каждый символ занимает 2 байта памяти - и EncodedString может занимать 1 байт на символ).

EncodedString можно преобразовать в byte [] и в System.String. Комментарии в коде проливают свет, а также объясняют пример, когда неявное преобразование может быть опасным.

Обычно вам нужна очень веская причина для объявления любых операторов преобразования, потому что.

Дополнительную информацию можно найти на 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();
    }
}
Константин Спирин
источник