映射不同类型对象的通用方法

时间:2014-12-31 08:58:01

标签: c#

我想编写将List映射到新列表的Generic Method,类似于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,但我不能再这样做了。我认为问题在于未声明的类型,编译器无法弄清楚它应该如何映射对象,但我不知道如何解决这个问题。所有示例和教程我都找到了相同类型的map对象。

3 个答案:

答案 0 :(得分:2)

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();

答案 1 :(得分: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。这应该清楚地表明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);
    }
}

答案 2 :(得分:1)

您的代码存在的问题是Predicate<T>是一个委托,它返回一个布尔值,然后您尝试添加到List<object>

使用Func<T,object>可能是您正在寻找的。

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

  • 转换为object不太有用
  • T映射到匿名类型的代理人传递给他人不会有帮助 - 您仍然会收到object后面没有任何有用属性的内容。
  • 您可能希望在方法中添加TResult泛型类型参数,并将Func<T, TResult>作为参数。