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

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

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


person esac    schedule 10.09.2009    source источник


Ответы (4)


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

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

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

or

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

or

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;
}
person Charles Bretana    schedule 10.09.2009

Вы можете объявить операторы преобразования в своем классе, используя либо explicit, либо _ 2_ ключевые слова.

Как правило, вы должны использовать 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;
person LukeH    schedule 10.09.2009

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

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

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

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

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

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

person Chris Chilvers    schedule 10.09.2009
comment
Глядя на пример, с которым вы связались, кажется, что при каждом приведении создается новый экземпляр объекта. Есть идеи, как просто выполнять операции типа get / set для текущего члена класса? - person esac; 11.09.2009

Для поддержки кастомного приведения вам необходимо предоставить операторы приведения (явные или неявные). Следующий пример класса 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();
    }
}
person Konstantin Spirin    schedule 10.09.2009