Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return <TEnumerable, T> : where TEnumerable:IEnumerable<T>

Goal: Generic enumerated type to be the same type when returned.

Note: This works when the types are entered but I don't understand why they can't be inferred.

List<T> then return List<T>

IOrderedEnumerable<T> then return IOrderedEnumerable<T>

ETC

Current method (works only if all types are entered)

public static TEnumerable WithEach<TEnumerable, T>(this TEnumerable items, Action<T> action)
where TEnumerable : IEnumerable<T>
{
    foreach (var item in items) action.Invoke(item);
    return items;
}

Example Only

var list = new List<int>(); //TODO: Mock random values
list.WithEach(x => Console.WriteLine(x)) //Here WithEach ideally returns List<int> following orignal type List<int>
    .OrderBy(x => x) 
    .WithEach(x => Console.WriteLine(x)); //Here WithEach ideally returns IOrderedEnumerable<int> following OrderBy

Making it work

var list = new List<int>(); //TODO: Mock random values
list.WithEach<List<int>, int>(x => Console.WriteLine(x))
    .OrderBy(x => x) 
    .WithEach<IOrderedEnumerable<int>, int>(x => Console.WriteLine(x));

What I'm missing is why C# can't infer the types although the where filter does make the types accurate. I understand why you either supply all or none generic types to methods so please do not point me to those answers.

Edit: If I can't infer the types; then how can I make this more elegant?

like image 848
Michael Puckett II Avatar asked Dec 04 '18 19:12

Michael Puckett II


People also ask

What is the return type of IEnumerable?

IEnumerable has just one method called GetEnumerator. This method returns another type which is an interface that interface is IEnumerator. If we want to implement enumerator logic in any collection class, it needs to implement IEnumerable interface (either generic or non-generic).

Is IEnumerable immutable?

Just realized IEnumerable is immutable, what are other commonly used immutable generic interfaces?


1 Answers

Type inference in C# is very complicated - just for once, I'm not going to get the spec out to try to step through it, because I'm aware of just how horrible it can become.

I believe the problem is that neither of the parameter/argument combinations gives the compiler enough information to infer T:

  • The TEnumerable items parameter doesn't mention T, so it isn't used to infer T, despite the type constraint
  • The Action<T> parameter would be fine, but the compiler can't make an inference based on the lambda expression you're providing

I can't think of a good change to the method signature that would make exactly your first code work - but you can change how you invoke the method just a little to make it work, by specifying the parameter type in the lambda expression:

var list = new List<int>();
list.WithEach((int x) => Console.WriteLine(x++))
    .OrderBy(x => x) 
    .WithEach((int x) => Console.WriteLine(x));

The downside of that is that it won't work with anonymous types, of course.

One workaround for that downside is a pretty horrible one, but it lets you express the type of T via a parameter instead, when you need to. You change the method signature to:

public static TEnumerable WithEach<TEnumerable, T>(
    this TEnumerable items,
    Action<T> action,
    T ignored = default(T))

If you wanted to call the method with a list of some anonymous type, you could write:

list.WithEach(x => Console.WriteLine(x.Name), new { Name = "", Value = 10 });

... where the final argument would match the anonymous type. That will allow the type of T to be inferred by the final parameter instead of the second one. You can use that for other types of course, but I'd probably stick to using it for anonymous types instead.

That's all a pretty horrible hack, and I don't think I'd actually use it, but if you really, really need this to work with anonymous types, it would cope.

like image 176
Jon Skeet Avatar answered Oct 12 '22 16:10

Jon Skeet