Semplifica il metodo generico

Oct 16 2020

Sto scrivendo un GenericDeserializer per Apache Kafka. La mia classe implementa IDeserializer<T>da Confluent.Kafka.Netpackage. Ho bisogno di fornire un metodo Deserialize che ha questa firma, T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context). Ma ho anche bisogno di usare la Deserializersclasse di Confluent perché implementa alcuni dettagli di basso livello come la decodifica di un messaggio big endian dalla rete come tipi primitivi, come array di byte, come stringhe UTF8 ecc. Come posso semplificare questo metodo. Un esempio di semplificazione potrebbe essere la rimozione dell'uso dei cast che ho introdotto per rendere felice il compilatore.

La mia logica in questo metodo è così, usa ogni tipo supportato nella Deserializersclasse già implementata . Per altri tipi che non sono deserializzati con l'aiuto di questa classe, usa la serializzazione Json. Ecco il mio codice:

public class GenericDeserializer<T> : IDeserializer<T>
{
    public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
    {
        var type = typeof(T);

        if (type == typeof(double))
        {
            var retVal = Deserializers.Double.Deserialize(data, isNull, context);

            return (T) (object) retVal;
        }
        if (type == typeof(float))
        {
            var retVal = Deserializers.Single.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (type == typeof(int))
        {
            var retVal = Deserializers.Int32.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (type == typeof(long))
        {
            var retVal = Deserializers.Int64.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (type == typeof(Null))
        {
            var retVal = Deserializers.Null.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (type == typeof(string))
        {
            var retVal = Deserializers.Utf8.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (type == typeof(byte[]))
        {
            var retVal = Deserializers.ByteArray.Deserialize(data, isNull, context);

            return (T) (object) retVal; 
        }
        if (isNull)
        {
            return default;
        }
        
        return JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions()
        {
            PropertyNameCaseInsensitive = true
        });
    }
}

Ecco la fonte della Deserializersclasse daConfluent.Kafka https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Deserializers.cs

Risposte

3 PeterCsala Oct 16 2020 at 21:46

È possibile definire una mappatura tra se Typeil file Deserializers. Puoi farlo ad esempio in questo modo:

public class GenericDeserializer<T> : IDeserializer<T>
{
    private readonly ImmutableDictionary<Type, object> _deserializers =
        new Dictionary<Type, object>
    {
        { typeof(double), Deserializers.Double },
        { typeof(float), Deserializers.Single },
        { typeof(int), Deserializers.Int32 },
        { typeof(long), Deserializers.Int64 },
        { typeof(Null), Deserializers.Null },
        { typeof(string), Deserializers.Utf8 },
        { typeof(byte[]), Deserializers.ByteArray },
    }.ToImmutableDictionary();
}

Non è possibile utilizzare il IDeserializer<T>parametro del tipo di valore nel dizionario, ecco perché è un file object.

Quindi tutto ciò che devi fare è effettuare una chiamata di ricerca e provare a trasmettere il valore a IDeserializer<T>

if (_deserializers.ContainsKey(typeof(T)))
{
    var deserializer = _deserializers[typeof(T)] as IDeserializer<T>;
    ...
}

Se il tipo è stato trovato, puoi creare ramificazioni in base al isNullvalore:

if (_deserializers.ContainsKey(typeof(T)))
{
    var deserializer = _deserializers[typeof(T)] as IDeserializer<T>;
    var retVal = deserializer.Deserialize(data, isNull, context);
    return !isNull ? retVal : default;
}

Se non viene trovato, puoi utilizzare JsonSerializercome fallback.

return JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions()
{
    PropertyNameCaseInsensitive = true
});

Il codice finale sarebbe simile a questo:

public class GenericDeserializer<T> : IDeserializer<T>
{
    private readonly ImmutableDictionary<Type, object> _deserializers =
        new Dictionary<Type, object>
    {
        { typeof(double), Deserializers.Double },
        { typeof(float), Deserializers.Single },
        { typeof(int), Deserializers.Int32 },
        { typeof(long), Deserializers.Int64 },
        { typeof(Null), Deserializers.Null },
        { typeof(string), Deserializers.Utf8 },
        { typeof(byte[]), Deserializers.ByteArray },
    }.ToImmutableDictionary();

    public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
    {
        if (_deserializers.ContainsKey(typeof(T)))
        {
            var deserializer = _deserializers[typeof(T)] as IDeserializer<T>;
            var retVal = deserializer.Deserialize(data, isNull, context);
            return !isNull ? retVal : default;
        }

        return JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions()
        {
            PropertyNameCaseInsensitive = true
        });
    }
}
slepic Oct 16 2020 at 12:18

Puoi sbarazzarti delle ramificazioni nel Deserializemetodo e spostare invece quella logica in un luogo che viene eseguito una sola volta: il luogo di costruzione. Un'istanza della tua GenericDeserializer<T>eseguirà sempre lo stesso ramo per qualsiasi input, perché l'istanza è già legata a uno specifico tipo di output T.Per i tipi come int e double dove usi la loro implementazione restituisci semplicemente la loro implementazione, usa la tua implementazione per il riposo.

class JsonDeserializer<T> : IDeserializer<T>
{
    public T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context)
    {
        if (isNull)
        {
            return default;
        }
        
        return JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions()
        {
            PropertyNameCaseInsensitive = true
        });
    }
}


IDeserializer<T> CreateDeserializer<T>()
{
    var type = typeof(T);

    if (type == typeof(double))
    {
        return (IDeserializer<T>) Deserializers.Double;
    }

    if (type === typeof(long))
    {
        return (IDeserializer<T>) Deserializers.Int64;
    }

    // ...

    return new JsonDeserializer<T>();
}