Упростить общий метод

Oct 16 2020

Я пишу GenericDeserializer для Apache Kafka. Мой класс реализует IDeserializer<T>из Confluent.Kafka.Netпакета. Мне нужно поставить метод десериализации , который имеет эту подпись, T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context). Но мне также нужно использовать Deserializersкласс Confluent, потому что он реализует некоторые детали низкого уровня, такие как декодирование сообщения с прямым порядком байтов из сети как примитивные типы, как массив байтов, как строка UTF8 и т. Д. Как я могу упростить этот метод. Одним из примеров упрощения может быть отказ от использования приведений, которые я ввел, чтобы сделать компилятор счастливым.

Моя логика в этом методе такова: используйте все поддерживаемые типы в уже реализованном Deserializersклассе. Для других типов, которые не десериализованы с помощью этого класса, используйте сериализацию Json. Вот мой код:

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

Вот источник Deserializersкласса отConfluent.Kafka https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Deserializers.cs

Ответы

3 PeterCsala Oct 16 2020 at 21:46

Вы можете определить соответствие между Types и Deserializers. Сделать это можно, например, так:

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

Вы не можете использовать IDeserializer<T>параметр типа Value в словаре, поэтому это файл object.

Затем все, что вам нужно сделать, это выполнить поисковый вызов и попытаться привести значение к IDeserializer<T>

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

Если тип был найден, вы можете выполнить ветвление по isNullзначению:

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

Если он не найден, вы можете использовать его JsonSerializerкак запасной вариант.

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

Окончательный код будет выглядеть так:

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

Вы можете избавиться от ветвления в Deserializeметоде и вместо этого переместить эту логику в место, которое выполняется только один раз, - в место построения. Ваш экземпляр GenericDeserializer<T>всегда будет выполнять одну и ту же ветвь для любого ввода, потому что экземпляр уже привязан к определенному типу вывода T. Для таких типов, как int и double, где вы используете их реализацию, просто верните их реализацию, используйте вашу реализацию для остальные.

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