如何转换查找的TElement;即ILookup <tkey,派生=“”>到ILookup <tkey,base =“”>?

时间:2018-07-18 09:06:43

标签: c# linq

我有一个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 ILookup<TKey, TElement> be (declared) covariant in TElement?biggeRyszard Dżegan answers主要是出于历史原因:ILookup<TKey, TElement>是在具有协方差的泛型之前开发的。
Herzmeistersomething similar索取Dictionary<TKey, TValue>Mehrdad Afshari answers认为,对于可变字典,协方差并不安全。
确实,如果Ilookup<TKey, TElement>TElement是协变的,那么我就不会遇到ILookup<TKey, TElement>强制转换问题的这种情况;但事实并非如此,所以我对更好方法的追求仍然在继续。

注意:我当然可以编写一个扩展方法来做到这一点,但这不会阻止迭代和重新打包所需的计算工作。

2 个答案:

答案 0 :(得分:3)

您可以创建代理:

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(就像您的示例一样,该键不支持键的值类型)。

答案 1 :(得分:1)

如何创建自己的实现console.log(PizzaSize.MEDIUM); // prints 'MEDIUM' 的类,如下所示:

ILookup<TKey, TBase>