Uprość metodę ogólną

Oct 16 2020

Piszę GenericDeserializer dla Apache Kafka. Moja klasa implementuje IDeserializer<T>z Confluent.Kafka.Netpakietu. Muszę podać metodę Deserialize, która ma ten podpis T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context). Ale muszę również użyć Deserializersklasy Confluent, ponieważ implementuje ona pewne szczegóły niskiego poziomu, takie jak dekodowanie wiadomości big endian z sieci jako typy pierwotne, jako tablica bajtów, jako łańcuch UTF8 itp. Jak mogę uprościć tę metodę. Jednym z przykładów uproszczenia może być usunięcie rzucania, które wprowadziłem, aby uszczęśliwić kompilator.

Moja logika w tej metodzie jest taka, użyj każdego obsługiwanego typu w już zaimplementowanej Deserializersklasie. W przypadku innych typów, które nie są deserializowane za pomocą tej klasy, użyj serializacji Json. Oto mój kod:

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

Oto źródło Deserializersklasy zConfluent.Kafka https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Deserializers.cs

Odpowiedzi

3 PeterCsala Oct 16 2020 at 21:46

Możesz zdefiniować mapowanie między Types i Deserializers. Możesz to zrobić na przykład w ten sposób:

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

Nie możesz użyć IDeserializer<T>parametru typu wartości w słowniku, dlatego jest to object.

Następnie wszystko, co musisz zrobić, to wykonać wywołanie wyszukiwania i spróbować rzutować wartość na IDeserializer<T>

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

Jeśli typ został znaleziony, możesz wykonać rozgałęzienie na podstawie isNullwartości:

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

Jeśli nie zostanie znaleziony, możesz użyć JsonSerializerjako rozwiązania zastępczego.

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

Ostateczny kod wyglądałby następująco:

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

Możesz pozbyć się rozgałęzień w Deserializemetodzie i zamiast tego przenieść tę logikę w miejsce, które jest wykonywane tylko raz - miejsce konstrukcji. Instancja twojej GenericDeserializer<T>woli zawsze wykonuje tę samą gałąź dla dowolnego wejścia, ponieważ instancja jest już powiązana z określonym typem wyjścia T. Dla typów takich jak int i double, gdzie używasz ich implementacji, po prostu zwróć ich implementację, użyj swojej implementacji odpoczynek.

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