Comment fournir une prise en charge personnalisée de la diffusion pour ma classe?

103

Comment puis-je fournir une assistance pour la diffusion de ma classe vers d'autres types? Par exemple, si j'ai ma propre implémentation de la gestion de a byte[], et que je veux permettre aux gens de convertir ma classe en a byte[], ce qui renverra simplement le membre privé, comment ferais-je cela?

Est-ce une pratique courante de les laisser également convertir cela en une chaîne, ou devrais-je simplement remplacer ToString()(ou les deux)?

esac
la source

Réponses:

114

Vous devrez remplacer l'opérateur de conversion, en utilisant l'un implicitou l' autre ou explicitselon que vous souhaitez que les utilisateurs doivent le caster ou si vous souhaitez que cela se produise automatiquement. Généralement, une direction fonctionnera toujours, c'est là que vous utilisez implicit, et l'autre direction peut parfois échouer, c'est là que vous utilisezexplicit .

La syntaxe est la suivante:

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

Pour votre exemple, dites à partir de votre type personnalisé ( MyType-> byte[]fonctionnera toujours):

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
la source
36

Vous pouvez déclarer des opérateurs de conversion sur votre classe à l'aide des mots clés explicitou implicit.

En règle générale, vous ne devez fournir implicitdes opérateurs de conversion que lorsque la conversion ne peut pas échouer. Utilisez explicitdes opérateurs de conversion lorsque la conversion peut échouer.

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

Utiliser explicitsignifie que les utilisateurs de votre classe devront effectuer une conversion explicite:

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;

L'utilisation implicitsignifie que les utilisateurs de votre classe n'ont pas besoin d'effectuer une conversion explicite, tout se passe de manière 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
la source
6

Je préfère avoir une méthode qui fera cela plutôt que de surcharger l'opérateur de cast.

Voir c # explicite et implicite mais notez qu'à partir de cet exemple, en utilisant la méthode explicite, si vous faites:

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

Alors tout va bien; cependant, si vous utilisez:

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

Vous obtiendrez maintenant une InvalidCastException car la chaîne ne peut pas être convertie en Role, pourquoi, le compilateur ne recherche que les conversions implicites / explicites au moment de la compilation en fonction de leur type compilé. Dans ce cas, le compilateur voit le nom comme un objet plutôt qu'une chaîne, et n'utilise donc pas l'opérateur surchargé de Role.

Chris Chilvers
la source
En regardant l'exemple auquel vous avez lié, il semble créer une nouvelle instance de l'objet à chaque distribution. Une idée comment faire simplement des opérations de type get / set sur un membre actuel de la classe?
esac
3

Pour la prise en charge personnalisée de la distribution, vous devez fournir des opérateurs de conversion (explicites ou implicites). L'exemple suivant de classe EncodedString est une implémentation simpliste de chaîne avec un encodage personnalisé (peut être utile si vous devez traiter des chaînes énormes et rencontrer des problèmes de consommation de mémoire car les chaînes .Net sont Unicode - chaque caractère prend 2 octets de mémoire - et EncodedString peut prendre 1 octet par caractère).

EncodedString peut être converti en byte [] et en System.String. Les commentaires dans le code éclairent et expliquent également un exemple où la conversion implicite peut être dangereuse.

Habituellement, vous avez besoin d'une très bonne raison pour déclarer les opérateurs de conversion en premier lieu car.

Des lectures complémentaires sont disponibles sur 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
la source