C# 使用泛型处理可为null的与不可为null的类型
我创建了一个C# 使用泛型处理可为null的与不可为null的类型,c#,json,generics,serialization,C#,Json,Generics,Serialization,我创建了一个LookupConverter:JsonConverter类来执行ILookup对象的JSON序列化和反序列化。正如您可能想象的那样,它有一些复杂性,必须处理泛型,并且由于缺少公共的具体的查找类。为了提高性能,它将其特定于类型的反射工作缓存在静态泛型类中。它工作得很好 嗯,几乎完美。我今天才意识到,它无法序列化包含空键的ILookup。经过一些思考和认识,在JSON中,没有简单的方法来表示对象中的空键(因为每个键都转换为字符串),我想我应该把输出对象稍微大一点 如果前一个输出是,例如
LookupConverter:JsonConverter
类来执行ILookup
对象的JSON序列化和反序列化。正如您可能想象的那样,它有一些复杂性,必须处理泛型,并且由于缺少公共的具体的查找
类。为了提高性能,它将其特定于类型的反射工作缓存在静态泛型类中。它工作得很好
嗯,几乎完美。我今天才意识到,它无法序列化包含空键的ILookup
。经过一些思考和认识,在JSON中,没有简单的方法来表示对象中的空键(因为每个键都转换为字符串),我想我应该把输出对象稍微大一点
如果前一个输出是,例如,{“key1”:[1,2,3]}
,那么我认为新的输出可以像{Groupings:{“key1”:[1,2,3]},NullKeyValue:[4,5,6]}
。这很尴尬,但到目前为止还不错。或者它可以是[{“key”:“key1”,“values”:[1,2,3]},{“key”:null,“values”:[4,5,6]}]
。不管怎样都没什么大不了的
为此添加序列化是轻而易举的事
然而,当反序列化的时候,我遇到了一个问题。我以前的反序列化程序非常简单(这里有一些复杂的缓存,试着忽略它,只需看到我的函数接受一个jObject
和一个serializer
,并返回一个正确类型的对象,它的用法类似于lookupmaker(jObject.Load(reader),serializer);
但是现在我在上面的Add
中得到一个错误:
参数类型“null”不可分配给参数类型“TKey”
当然不是。它不能保证TKey
不是一个不可为空的值类型。太好了。我将在静态类GenericMethodCache
上抛出一个约束其中TKey:class
…只是,当我想要一个:struct
版本时,我遇到了麻烦,因为Gener>的整个要点icMethodCache
是为了防止使用对象的序列化程序代码必须处理泛型部分。我无法获得自动解析,因为解析无法使用类型约束来区分方法组。突然,这个问题的复杂性爆发了,我不确定是否要继续深入丛林让它工作是有意义的,所以我正在寻求指导
由于这是一个相当复杂的场景,下面是不处理空键的完整代码(下一步介绍FunctionResultCache):
公共密封类LookupConverter:JsonConverter{
//ReSharper禁用一次收集未更新。本地
专用静态只读函数ResultCache s_typeCanConvertDictionary=
新函数ResultCache(类型=>
新[]{type}
.Concat(类型.GetInterfaces())
.Any(iface=>iface.IsGenericType&&iface.GetGenericTypeDefinition()==typeof(ILookup))
);
公共覆盖布尔CanConvert(类型objectType)=>s_类型CanConvertDictionary[objectType];
public override bool CanWrite=>true;
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器){
writer.WriteStartObject();
变量分组=(IEnumerable)值;
var getKey=_keyFetcherForType[value.GetType()];
foreach(分组中的动态分组){
writer.WritePropertyName(getKey(grouping.ToString());
序列化(writer,(IEnumerable)分组);
}
writer.WriteEndObject();
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)=>
//ReSharper禁用once AccessToStaticMemberViaderiedType
_反序列化器FORTYPE[objectType](JObject.Load(reader),序列化器);
私有静态类GenericMethodCache{
公共静态函数GetLookupMaker()=>
(jObject,序列化程序)=>((IEnumerable)jObject)
.SelectMany(
kvp=>kvp.Value.ToObject(),
(kvp,value)=>新的KeyValuePair(Convert(kvp.Key),value)
)
.ToLookup(kvp=>kvp.Key,kvp=>kvp.Value);
公共静态函数GetKeyFetcher()=>
分组=>((i分组)分组)
钥匙
专用静态T转换(字符串输入){
试一试{
返回(T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(输入);
}
捕获(不支持异常){
返回默认值(T);
}
}
}
//ReSharper禁用一次收集未更新。本地
私有只读函数ResultCache\u反序列化器FORTYPE=
新函数ResultCache(类型=>{
var genericMethodCache=typeof(genericMethodCache).MakeGenericType(type.GetGenericArguments());
return(Func)genericMethodCache.GetMethod(nameof(genericMethodCache.GetLookupMaker)).Invoke(null,新对象[0]);
}
);
//ReSharper禁用一次收集未更新。本地
私有只读函数ResultCache\u keyFetcherForType=
新函数ResultCache(类型=>{
var genericMethodCache=typeof(genericMethodCache).MakeGenericType(type.GetGenericArguments());
return(Func)genericMethodCache.GetMethod(nameof(genericMethodCache.GetKeyFetcher)).Invoke(null,新对象[0]);
}
);
}
FunctionResultCache
基本上只是一个字典
,它具有一个特殊属性,即当您索引到一个不存在的键时,它会运行一个函数(在构造函数中传递)要获取该值,然后存储并缓存该值,并将其返回给您,因此下次索引到同一个键时,它将返回缓存的值
我很抱歉这个问题和代码太长了。这是一个有点复杂的场景,为了得到有用的反馈,我必须展示一些关于发生了什么的细节
还有一点要注意:public static Func<JObject, JsonSerializer, object> GetLookupMaker() =>
(jObject, serializer) => ((IEnumerable<KeyValuePair<string, JToken>>) jObject)
.SelectMany(
kvp => kvp.Value.ToObject<List<TValue>>(),
(kvp, value) => new KeyValuePair<TKey, TValue>(Convert<TKey>(kvp.Key), value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
var list = new List<KeyValuePair<TKey, List<TValue>>>();
var nullKeyValue = jObject["NullKeyValue"];
if (nullKeyValue != null) {
list.Add(new KeyValuePair<TKey, List<TValue>>(null, nullKeyValue.ToObject<List<TValue>>()));
} // ^^^^ this null
// Then here append the items from jObject["Groupings"], and finally ToLookup.
public sealed class LookupConverter : JsonConverter {
// ReSharper disable once CollectionNeverUpdated.Local
private static readonly FunctionResultCache<Type, bool> s_typeCanConvertDictionary =
new FunctionResultCache<Type, bool>(type =>
new [] { type }
.Concat(type.GetInterfaces())
.Any(iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(ILookup<,>))
);
public override bool CanConvert(Type objectType) => s_typeCanConvertDictionary[objectType];
public override bool CanWrite => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteStartObject();
var groupings = (IEnumerable) value;
var getKey = _keyFetcherForType[value.GetType()];
foreach (dynamic grouping in groupings) {
writer.WritePropertyName(getKey(grouping).ToString());
serializer.Serialize(writer, (IEnumerable) grouping);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
// ReSharper disable once AccessToStaticMemberViaDerivedType
_deserializerForType[objectType](JObject.Load(reader), serializer);
private static class GenericMethodCache<TKey, TValue> {
public static Func<JObject, JsonSerializer, object> GetLookupMaker() =>
(jObject, serializer) => ((IEnumerable<KeyValuePair<string, JToken>>) jObject)
.SelectMany(
kvp => kvp.Value.ToObject<List<TValue>>(),
(kvp, value) => new KeyValuePair<TKey, TValue>(Convert<TKey>(kvp.Key), value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
public static Func<object, object> GetKeyFetcher() =>
grouping => ((IGrouping<TKey, TValue>) grouping)
.Key;
private static T Convert<T>(string input) {
try {
return (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
}
catch (NotSupportedException) {
return default(T);
}
}
}
// ReSharper disable once CollectionNeverUpdated.Local
private readonly FunctionResultCache<Type, Func<JObject, JsonSerializer, object>> _deserializerForType =
new FunctionResultCache<Type, Func<JObject, JsonSerializer, object>>(type => {
var genericMethodCache = typeof(GenericMethodCache<,>).MakeGenericType(type.GetGenericArguments());
return (Func<JObject, JsonSerializer, object>) genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetLookupMaker)).Invoke(null, new object[0]);
}
);
// ReSharper disable once CollectionNeverUpdated.Local
private readonly FunctionResultCache<Type, Func<object, object>> _keyFetcherForType =
new FunctionResultCache<Type, Func<object, object>>(type => {
var genericMethodCache = typeof(GenericMethodCache<,>).MakeGenericType(type.GetGenericArguments());
return (Func<object, object>) genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher)).Invoke(null, new object[0]);
}
);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteStartArray();
var groupings = (IEnumerable) value;
var getKey = _keyFetcherForType[value.GetType()];
foreach (dynamic grouping in groupings) {
writer.WriteStartObject();
writer.WritePropertyName("key");
object key = getKey(grouping);
if (key == null) {
writer.WriteNull();
} else {
serializer.Serialize(writer, key);
}
writer.WritePropertyName("values");
serializer.Serialize(writer, (IEnumerable) grouping);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
// -- snip --- //
private static class GenericMethodCache<TKey, TValue> {
public static Func<JArray, JsonSerializer, object> GetLookupMaker() =>
(jArray, serializer) =>
jArray
.Children()
.Select(jObject => new {
Key = jObject["key"].ToObject<TKey>(),
Values = jObject["values"].ToObject<List<TValue>>()
})
.SelectMany(
kvp => kvp.Values,
(kvp, value) => new KeyValuePair<TKey, TValue>(kvp.Key, value)
)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
public static Func<object, object> GetKeyFetcher() =>
grouping => ((IGrouping<TKey, TValue>) grouping).Key;
}