【问题标题】:Generic method to map objects of different types映射不同类型对象的通用方法
【发布时间】:2015-01-13 13:49:43
【问题描述】:

我想编写将 List 映射到新列表的通用方法,类似于 JS 的 map 方法。然后我会像这样使用这种方法:

var words= new List<string>() { "Kočnica", "druga beseda", "tretja", "izbirni", "vodno bitje" };
List<object> wordsMapped = words.Map(el => new { cela = el, končnica = el.Končnica(5) });

我知道有 Select 方法可以做同样的事情,但我需要编写自己的方法。现在我有这个:

public static IEnumerable<object> SelectMy<T>(this IEnumerable<T> seznam, Predicate<T> predicate)
{
    List<object> ret = new List<object>();

    foreach (var el in seznam)
        ret.Add(predicate(el));

    return ret;
}

我也知道我可以使用yield return,但我不能再次使用。我认为问题在于未声明的类型,编译器无法弄清楚它应该如何映射对象,但我不知道如何解决这个问题。我找到了相同类型的地图对象的所有示例和教程。

【问题讨论】:

  • 你只是想给Select写一个相同的扩展方法吗?此外,你似乎有很多奇怪的箍要跳过。不能用Select,不能用yield return...这是某种作业吗?
  • 为了将来参考,如果您遇到编译器错误,您应该发布它。

标签: c#


【解决方案1】:

Linq 的Select 等效于其他函数式语言中的map() 函数。映射函数通常不会被称为Predicate,IMO - 谓词将是一个可以减少集合的过滤器。

您当然可以包装一个扩展方法,该方法将应用投影以将输入映射到输出(两者都可以是匿名类型):

public static IEnumerable<TO> Map<TI, TO>(this IEnumerable<TI> seznam, 
                                          Func<TI, TO> mapper)
{
    foreach (var item in seznam)
        yield return mapper(item);
}

相当于

public static IEnumerable<TO> Map<TI, TO>(this IEnumerable<TI> seznam, 
                                          Func<TI, TO> mapper)
{
    return seznam.Select(mapper);
}

如果你不想要一个强返回类型,你可以将输出类型保留为object

public static IEnumerable<object> Map<TI>(this IEnumerable<TI> seznam, Func<TI, object> mapper)
{
   // Same implementation as above

这样称呼:

var words = new List<string>() { "Kočnica", "druga beseda", "tretja", "izbirni", "vodno bitje" };
var wordsMapped = words.Map(el => new { cela = el, končnica = el.Končnica(5) });

编辑
如果您喜欢动态语言的运行时刺激,您还可以使用dynamic 代替object

但是像这样使用dynamic 所以这个precludes the using the sugar 的扩展方法像Končnica - Končnica 要么需要是所有使用的类型的方法,要么被显式调用,例如

static class MyExtensions
{
    public static int Končnica(this int i, int someInt)
    {
        return i;
    }

    public static Foo Končnica(this Foo f, int someInt)
    {
        return f;
    }

    public static string Končnica(this string s, int someInt)
    {
        return s;
    }
}

然后,如果您输入中的所有项目都实现了Končnica,您可以调用:

var things = new List<object>
{ 
   "Kočnica", "druga beseda", 
    53, 
    new Foo() 
};

var mappedThings = things.Map(el => new 
{ 
     cela = el, 
     končnica = MyExtensions.Končnica(el, 5) 
     // Or el.Končnica(5) IFF it is a method on all types, else run time errors ...
})
.ToList();

【讨论】:

  • 谢谢,你是第一个回答的,所以我接受了你的回答,尽管其他答案和你的一样有帮助。
  • 我会尽量避免使用 dynamic - 如果您的预测都是在强类型上完成的,那么强类型 &lt;TI, TO&gt; 将是可行的方法。
【解决方案2】:

您可以像这样修复您的代码以使其正常工作:

public static IEnumerable<TResult> SelectMy<T, TResult>(this IEnumerable<T> seznam, 
                                                        Func<T, TResult> mapping)
{
    var ret = new List<TResult>();

    foreach (var el in seznam)
    {
        ret.Add(mapping(el));
    }    

    return ret;
}

请注意,与典型的 Linq 扩展相比,这效率低下且存在问题,因为它一次枚举整个输入。如果输入是一个无限序列,那么你的处境就很糟糕。

不使用yield 也可以解决这个问题,但是会有些冗长。我认为如果你能告诉我们所有人为什么你试图用两只手绑在背后来完成这项任务,那将是理想的。


作为奖励,您可以通过yield 的惰性评估优势来实现这一点,而无需实际使用产量。这应该非常清楚yield 的价值:
internal class SelectEnumerable<TIn, TResult> : IEnumerable<TResult>
{
    private IEnumerable<TIn> BaseCollection { get; set; }
    private Func<TIn, TResult> Mapping { get; set; }

    internal SelectEnumerable(IEnumerable<TIn> baseCollection, 
                              Func<TIn, TResult> mapping)
    {
        BaseCollection = baseCollection;
        Mapping = mapping;
    }

    public IEnumerator<TResult> GetEnumerator()
    {
        return new SelectEnumerator<TIn, TResult>(BaseCollection.GetEnumerator(), 
                                                  Mapping);
    }

    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

internal class SelectEnumerator<TIn, TResult> : IEnumerator<TResult>
{
    private IEnumerator<TIn> Enumerator { get; set; }
    private Func<TIn, TResult> Mapping { get; set; }

    internal SelectEnumerator(IEnumerator<TIn> enumerator, 
                              Func<TIn, TResult> mapping)
    {
        Enumerator = enumerator;
        Mapping = mapping;
    }

    public void Dispose() { Enumerator.Dispose(); }

    public bool MoveNext() { return Enumerator.MoveNext(); }

    public void Reset() { Enumerator.Reset(); }

    public TResult Current { get { return Mapping(Enumerator.Current); } }

    object IEnumerator.Current { get { return Current; } }
}

internal static class MyExtensions
{
    internal static IEnumerable<TResult> MySelect<TIn, TResult>(
        this IEnumerable<TIn> enumerable,
        Func<TIn, TResult> mapping)
    {
        return new SelectEnumerable<TIn, TResult>(enumerable, mapping);
    }
}

【讨论】:

    【解决方案3】:

    您的代码的问题是 Predicate&lt;T&gt; 是一个返回布尔值的委托,然后您尝试将其添加到 List&lt;object&gt;

    使用Func&lt;T,object&gt; 可能是您想要的。

    话虽如此,那段代码闻起来很糟糕:

    • 转换为 object 没多大用处
    • 传递一个将 T 映射到匿名类型的委托将无济于事 - 您仍然会得到一个没有有用属性的 object 返回。
    • 您可能希望将TResult 泛型类型参数添加到您的方法中,并将Func&lt;T, TResult&gt; 作为参数。

    【讨论】:

    • 我同意所有 3 点,但任务只是演示如何使用我们自己的方法来映射 List 元素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多