映射不同类型的对象的泛型方法

本文关键字:对象 泛型方法 同类型 映射 | 更新日期: 2023-09-27 18:35:48

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

映射不同类型的对象的泛型方法

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,因此这排除了使用像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();

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

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

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

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

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

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