Uprość metodę ogólną
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
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
});
}
}
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>();
}