我创建了一个LookupConverter : JsonConverter类,以执行ILookup对象的JSON序列化和反序列化。如您所料,由于缺乏通用的具体Lookup类,它具有一定的复杂性,必须处理泛型。为了提高性能,它在静态泛型类中缓存其特定于类型的反射工作。它完美地工作!好吧,几乎完美。我今天才意识到,它不能解决序列化包含空ILookup的Key的问题。经过思考并意识到,在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并返回正确类型的对象,该对象的用法类似于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);好的,所以现在我在想,我将创建一个lookupmaker(JObject.Load(reader), serializer);的List,如果存在null键,则添加一个额外的值,然后像上面一样在其上抛出一个KeyValuePair: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.但是现在我在上面的ToLookup中收到一个错误:  参数类型“ null”不可分配给参数类型“ TKey”。好吧,当然不是。无法保证Add不是非空值类型。大。我只会在我的静态类TKey上抛出约束where TKey : class ...,仅当我想要一个GenericMethodCache<TKey, TValue>版本时,我就遇到了麻烦,因为: struct的重点是防止使用GenericMethodCache的序列化程序代码处理通用部分。我无法获得自动解析,因为解析无法使用类型约束来区分方法组。突然,它的复杂性爆炸了,我不确定继续深入丛林试图使其正常运行是有意义的,所以我正在寻求指导!由于这是一个非常复杂的场景,因此以下是不处理空键的完整代码(接下来是FunctionResultCache的更多信息):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]); } );}object基本上只是具有特殊属性的FunctionResultCache,当您索引不存在的键时,它会运行一个函数(传递给构造函数)以获取值,然后存储并缓存该值并返回它给您,因此下次您索引相同的键时,它将返回缓存的值。我很抱歉这个问题和代码的长度。这是一个有点复杂的场景,为了获得有用的反馈,我必须显示一些有关正在发生的事情的详细信息。附言关于此的一点说明:Dictionary。 genericMethodCache.GetMethod(nameof(GenericMethodCache<int, int>.GetKeyFetcher))不喜欢通用类型定义,例如nameof。它只喜欢通用类型GenericMethodCache<,>。但是,从长远来看,GenericMethodCache<int, int>将被忽略,并返回名称int, int。 最佳答案 您可以只用(TKey)(object)null代替(TKey)null。当然,如果NullReferenceException是不可为空的值类型,则将抛出TKey,但这似乎是有道理的,因为指定类型为ILookup >。关于c# - 使用泛型处理可空与不可空类型,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43669154/
10-11 16:07