【问题标题】:How to cast TElement of lookup; i.e. ILookup<TKey, Derived> to ILookup<TKey, Base>?如何投射查找的TElement;在。 Lookup<TKey, Derived> 到 ILookup<TKey, Base>?
【发布时间】:2018-07-18 18:56:14
【问题描述】:

我有一个ILookup&lt;int, Derived&gt;,我想返回一个ILookup&lt;int, Base&gt;,其中Derived 实现或扩展Base

目前我使用SelectMany(),然后ToLookup()先将ILookup&lt;int, Derived&gt;的键值对提取到一个平面IEnumerable中,然后新建一个ILookup&lt;int, Base&gt;

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&lt;TKey, TElement&gt; 是在具有协方差的泛型之前开发的。
Herzmeistersomething similar 询问 Dictionary&lt;TKey, TValue&gt;Mehrdad Afshari answers 对于可变字典,协方差是不安全的。
事实上,如果Ilookup&lt;TKey, TElement&gt;TElement 中是协变的,我就不会遇到ILookup&lt;TKey, TElement&gt; 铸造问题的这个实例;但事实并非如此,所以我仍在继续寻找更好的方法。

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

【问题讨论】:

  • @Tomer,我应该提到这个问题,但它与我通过 bigge 链接的问题相似。我认为不是重复的;是的,如果 ILookup 是协变的,我就不会问我的问题,但我并不是要求所有 ILookup/IDictionary 都是协变的,我只是想轻松地将这个特定的转换为我知道它是安全的。

标签: c# linq


【解决方案1】:

你可以创建一个代理:

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(不支持键的值类型,如您的示例中所示)。

【讨论】:

  • @PaulSuart Mine 是一个更透明的代理,你的是混合的,在GetEnumerator() 中使用SelectMany()(但最后GetEnumerator() 已经在枚举基础ILookup&lt;&gt; 所以这不是一个大问题)。 最终的结果并没有太大的不同
  • 啊,我意识到这甚至没有必要,可以删除。
【解决方案2】:

如何创建自己的实现ILookup&lt;TKey, TBase&gt; 的类,如下所示:

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

【讨论】:

    猜你喜欢
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多