如何投射查找的 TElement;即ILookup<TKey,派生>到ILookup<TKey,Base>?



我有一个ILookup<int, Derived>,我想返回一个ILookup<int, Base>,其中Derived实现或扩展了Base

目前我使用SelectMany(),然后使用ToLookup(),首先将ILookup<int, Derived>的键值对提取到平面IEnumerable中,然后创建新的ILookup<int, Base>:

class Base { }
class Derived: Base { }
class Test
{
ILookup<int, Base> CastLookup(ILookup<int, Derived> existing)
{
IEnumerable<KeyValuePair<int, Base>> flattened = existing.SelectMany(
(x) => x,
(gr, v) => new KeyValuePair<int, Base>(gr.Key, (Base)v));    // I know the explicit cast can be implicit here; it is just to demonstrate where the up casting is happening.
ILookup<int, Base> result = flattened.ToLookup(
(x) => x.Key,
(x) => x.Value);
return result;
}
}

如何转换ILookup而不迭代其条目,然后重新打包它们?


注意:一个相关的问题是Shouldn';t I查找<TKey、TElement>在TElement中是协变的?bigge。Ryszard Dżegan回答说,这主要是由于历史原因:ILookup<TKey, TElement>是在具有协方差的泛型之前开发的
Herzmeister对Dictionary<TKey, TValue>提出了类似的要求。MehrdadAfshari回答说,对于可变字典来说,协方差是不安全的
事实上,如果Ilookup<TKey, TElement>TElement中是协变的,我就不会遇到ILookup<TKey, TElement>选角问题的这个例子;但事实并非如此,所以我对更好方式的追求仍在继续

注意:我当然可以写一个扩展方法来实现这一点,但这并不妨碍迭代和重新打包所需的计算工作

您可以创建一个代理:

public static ILookup<TKey, TValueBase> ToLookupBase<TKey, TValue, TValueBase>(this ILookup<TKey, TValue> lookup)
where TValue : class, TValueBase
{
return new LookupProxy<TKey, TValue, TValueBase>(lookup);
}
public class LookupProxy<TKey, TValue, TValueBase> : ILookup<TKey, TValueBase>
where TValue : class, TValueBase
{
private readonly ILookup<TKey, TValue> lookup;
public LookupProxy(ILookup<TKey, TValue> lookup)
{
this.lookup = lookup;
}
public IEnumerable<TValueBase> this[TKey key] => lookup[key];
public int Count => lookup.Count;
public bool Contains(TKey key) => lookup.Contains(key);
public IEnumerator<IGrouping<TKey, TValueBase>> GetEnumerator() => lookup.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

请注意,您必须:

var base = existing.ToLookupBase<int, Derived, Base>();

所以明确地告诉所有的通用参数。如果您甚至想支持TKey的协方差,它会稍微复杂一点,并且需要一个单独的支持类和一个独立的方法:

public static ILookup<TKeyBase, TValueBase> ToLookupBase2<TKey, TValue, TKeyBase, TValueBase>(ILookup<TKey, TValue> lookup)
where TKey : class, TKeyBase
where TValue : class, TValueBase
{
return new LookupProxy2<TKey, TValue, TKeyBase, TValueBase>(lookup);
}
public class LookupProxy2<TKey, TValue, TKeyBase, TValueBase> : ILookup<TKeyBase, TValueBase>
where TKey : class, TKeyBase
where TValue : class, TValueBase
{
private readonly ILookup<TKey, TValue> lookup;
public LookupProxy2(ILookup<TKey, TValue> lookup)
{
this.lookup = lookup;
}
public IEnumerable<TValueBase> this[TKeyBase key] => key is TKey ? lookup[(TKey)key] : Enumerable.Empty<TValueBase>();
public int Count => lookup.Count;
public bool Contains(TKeyBase key) => key is TKey ? lookup.Contains((TKey)key) : false;
public IEnumerator<IGrouping<TKeyBase, TValueBase>> GetEnumerator() => lookup.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

这是因为您需要添加一个where TKey : class, TKeyBase(它不支持键的值类型,就像您的示例中一样(。

创建自己的类来实现ILookup<TKey, TBase>,如下所示:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var lookup1 = new List<Derived> { new Derived() { Key = 1, Prop1 = "A", Prop2 = "B"} } .ToLookup(x => x.Key, x => x);
var baseLookup = new BaseLookup<int, Base, Derived>(lookup1);
Console.WriteLine(baseLookup[1]);
}        
}
public class BaseLookup<TKey, TBase, TDerived> : ILookup<TKey, TBase> where TDerived : TBase
{
private readonly ILookup<TKey, TDerived> _inner;
public BaseLookup(ILookup<TKey, TDerived> inner)
{
_inner = inner;
}
IEnumerator<IGrouping<TKey, TBase>> IEnumerable<IGrouping<TKey, TBase>>.GetEnumerator()
{
return (IEnumerator<IGrouping<TKey, TBase>>) _inner.GetEnumerator();
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable) _inner).GetEnumerator();
}
public bool Contains(TKey key)
{
return _inner.Contains(key);
}
public int Count => _inner.Count;
public IEnumerable<TBase> this[TKey key] => _inner[key].Cast<TBase>();
}
public class Base
{
public int Key { get; set; }
public string Prop1 { get; set; }
}
public class Derived : Base
{
public string Prop2 { get; set; }
}
}

最新更新