Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/270.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用泛型处理可为null的与不可为null的类型_C#_Json_Generics_Serialization - Fatal编程技术网

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