【问题标题】:Wrapping method to make it generic with map method包装方法使其与 map 方法通用
【发布时间】:2014-01-08 05:55:19
【问题描述】:

我有一个或多或少看起来像这样的方法:

ConcreteType Find(IEnumerable<ConcreteType> values)

它迭代 IEnumerable 并在某些时候返回结果中包含的值之一。此方法不受我控制,我无法更改。

我需要将此方法包装在另一个应该如下所示的方法中:

T Find(IEnumerable<T> values, Func<T,ConcreteType> map)

有一个泛型类型T。此方法应调用上述方法,将所有值从T 映射到ConcreteType,然后将返回的ConcreteType 映射回原始T

最简单的方法是什么?

重要提示: 可枚举是动态评估的 (LINQ),map 方法可能很慢。因此,该解决方案不允许迭代可枚举的次数超过原始 Find 必须做的事情,并且也应该只为原始 Find 方法实际请求的值调用 map 方法。

您可以假设所有值(TConcreteType)都是唯一的,因此不可能将一个 ConcreteType 映射到各种 T,反之亦然。

示例:

如果上面说的太抽象,这里举个具体的例子:

// Original method
byte[] FindMatchingBuffer(IEnumerable<byte[]> values)

// Wrapper (TODO)
T FindMatchingBuffer(IEnumerable<T> values, Func<T, byte[]> map)

// Sample call
string matchingFileName = FindMatchingBuffer(fileNames, File.ReadAllBytes);

谢谢!

【问题讨论】:

  • 你能保证你不能改变的方法只会迭代到它需要的地方吗?换句话说,你能返回它获取的最后一个值吗?
  • @JonSkeet 我知道该方法只会根据需要进行迭代。所以,是的,我可以返回最后一个捕获的值。虽然我认为这样的解决方案对我来说有点肮脏......
  • 我会说,你天生就处于一个肮脏的境地。您是否有任何保证可以比较 ConcreteType 的实例是否相等?将ConcreteType 的所有映射实例存储在内存中是否有问题?在您给出的示例中,这两者都很痛苦 - 您可能不想保留您读取的所有文件的所有数据。
  • 是的,我知道 ConcreteType 的实例,因为我自己“生产”它们,并且该方法的假定“合同”是它总是返回列表中的一个项目或 null。具体类型都是不同的,可以通过引用进行比较。无需按值比较。
  • 另外,虽然必须将数据保存在内存中并不好,但这不会成为问题,因为所有值的完整总和预计会小于几兆字节,我已经无论如何都要缓存其中一些值,因为加载它们很昂贵。

标签: c# .net linq generics functional-programming


【解决方案1】:

这个答案等同于 C.Evenhuis 的答案,但使用的是迭代器块而不是整个单独的类:

T Find(IEnumerable<T> values, Func<T, ConcreteType> map)
{
    var dictionary = new Dictionary<ConcreteType, T>();
    ConcreteType found = Find(MapAndRecord(values, map, reverseMapping));
    // TODO: Handle the situation where it's not found. (Does Find
    // return null?)
    return dictionary[found];
}

private static IEnumerable<ConcreteType> MapAndRecord(
    IEnumerable<T> values, Func<T, ConcreteType> map,
    IDictionary<ConcreteType, T> reverseMapping)
{
    foreach (var value in values)
    {
        var mapped = map(value);
        reverseMapping[mapped] = value;
        yield return mapped;
    }
}

【讨论】:

    【解决方案2】:

    您可以实现IEnumerator&lt;Y&gt;(在您的示例中为byte[]),它将IEnumerator&lt;T&gt; 和map 函数作为构造函数参数,并保留所有映射项的Dictionary(因为我们不知道@ 987654325@ 找到匹配后停止迭代。

    Find 返回后,您可以通过TryGetInputValue 再次获取文件名。

    (我不是 LINQ 用户,但我确信这可以封装到 LINQable 中)

    public sealed class LookupEnumerator<T, Y> : IEnumerator<Y>
    {
        private readonly IEnumerator<T> input;
        private readonly Func<T, Y> map;
        private readonly Dictionary<Y, T> reverse = new Dictionary<Y, T>();
    
        public LookupEnumerator(IEnumerator<T> input, Func<T, Y> map)
        {
            this.input = input;
            this.map = map;
        }
    
        public Y Current
        {
            get;
            private set;
        }
    
        public bool TryGetInputValue(Y value, out T inputValue)
        {
            return reverse.TryGetValue(value, out inputValue);
        }
    
        object IEnumerator.Current
        {
            get { return Current; }
        }
    
        public bool MoveNext()
        {
            if (!input.MoveNext())
            {
                return false;
            }
    
            Current = map(input.Current);
            reverse.Add(Current, input.Current);
            return true;
        }
    
        // ... rest of IEnumerator implementation
    

    【讨论】:

    • 是的,这肯定行得通。我一直在寻找更简单的东西,虽然它不需要整个枚举器实现......
    猜你喜欢
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多