Generic method to map objects of different types

2020-03-26 11:54发布

I would like to write Generic Method that would map List to new list, similar to JS's map method. I would then use this method like this:

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

I know there's Select method which does the same thing but I need to write my own method. Right now I have this:

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

I also know I could use yield return but again I mustn't. I think the problem is with undeclared types and compiler can't figure out how it should map objects but I don't know how to fix that. All examples and tutorials I found map object of same types.

标签: c#
3条回答
混吃等死
2楼-- · 2020-03-26 12:05

The problem with your code is that Predicate<T> is a delegate that returns a boolean, which you're then trying to add to a List<object>.

Using a Func<T,object> is probably what you're looking for.

That being said, that code smells bad:

  • Converting to object is less than useful
  • Passing a delegate that maps T to an anonymous type won't help - you'll still get an object back which has no useful properties.
  • You probably want to add a TResult generic type parameter to your method, and take a Func<T, TResult> as an argument.
查看更多
【Aperson】
3楼-- · 2020-03-26 12:12

Linq's Select is the equivalent of the map() function in other functional languages. The mapping function would typically not be called Predicate, IMO - predicate would be a filter which could reduce the collection.

You can certainly wrap an extension method which would apply a projection to map input to output (either of which could be be anonymous types):

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

Which is equivalent to

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

And if you don't want a strong return type, you can leave the output type as object

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

And called like so:

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

Edit
If you enjoy the runtime thrills of dynamic languages, you could also use dynamic in place of object.

But using dynamic like this so this precludes the using the sugar of extension methods like Končnica - Končnica would either need to be a method on all of the types utilized, or be invoked explicitly, e.g.

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

And then, provided all items in your input implemented Končnica you could invoke:

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();
查看更多
女痞
4楼-- · 2020-03-26 12:24

You can fix your code to work correctly like this:

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

Note that this is inefficient and problematic compared to typical Linq extensions, because it enumerates the entire input at once. If the input is an infinite series, you are in for a bad time.

It is possible to remedy this problem without the use of yield, but it would be somewhat lengthy. I think it would be ideal if you could tell us all why you are trying to do this task with two hands tied behind your back.


As a bonus, here is how you could implement this with the lazy evaluation benefits of yield without actually using yield. This should make it abundantly clear just how valuable yield is:

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);
    }
}
查看更多
登录 后发表回答