Método genérico simplificado
Estou escrevendo um GenericDeserializer para Apache Kafka. Minha classe implementa IDeserializer<T>do Confluent.Kafka.Netpacote. Preciso fornecer um método de desserialização que tenha esta assinatura T Deserialize(ReadOnlySpan<byte> data, bool isNull, SerializationContext context),. Mas também preciso usar a Deserializersclasse de Confluent porque implementa alguns detalhes de baixo nível, como decodificar uma mensagem big endian da rede como tipos primitivos, como array de bytes, como string UTF8 etc. Como posso simplificar esse método. Um exemplo de simplificação pode ser a remoção do uso de casts que introduzi para deixar o compilador feliz.
Minha lógica neste método é assim, usar todos os tipos suportados na Deserializersclasse já implementada . Para outros tipos que não são desserializados com a ajuda desta classe, use Serialização Json. Aqui está o meu código:
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
});
}
}
Aqui está a fonte da Deserializersaula deConfluent.Kafka https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Deserializers.cs
Respostas
Você pode definir um mapeamento entre Typeseo Deserializers. Você pode fazer isso, por exemplo:
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();
}
Você não pode usar IDeserializer<T>no parâmetro de tipo Value do Dicionário, por isso é um object.
Então, tudo que você precisa fazer é fazer uma chamada de pesquisa e tentar lançar o valor para IDeserializer<T>
if (_deserializers.ContainsKey(typeof(T)))
{
var deserializer = _deserializers[typeof(T)] as IDeserializer<T>;
...
}
Se o tipo foi encontrado, você pode fazer ramificações com base no isNullvalor:
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 não for encontrado, você pode usar o JsonSerializercomo substituto.
return JsonSerializer.Deserialize<T>(data, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
O código final ficaria assim:
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
});
}
}
Você pode se livrar da ramificação no Deserializemétodo e, em vez disso, mover essa lógica para um local que é executado apenas uma vez - o local da construção. Uma instância sua GenericDeserializer<T>sempre executará o mesmo branch para qualquer entrada, porque a instância já está ligada a um tipo de saída específico T. Para os tipos como int e double, onde você usa sua implementação, apenas retorne sua implementação, use sua implementação para o descansar.
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>();
}