Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow support for params IEnumerable<T> methods #36

Closed
mburbea opened this issue Jan 19, 2015 · 99 comments
Closed

Allow support for params IEnumerable<T> methods #36

mburbea opened this issue Jan 19, 2015 · 99 comments

Comments

@mburbea
Copy link

mburbea commented Jan 19, 2015

Currently, C# only supports params T[], however it is rare that I actually need an array for my method. More often than not, I merely want iteration or to perform some sort of LINQ operators against the parameter. In fact, it is common for libraries to declare a version of the method that takes an IEnumerable and a T[], and have the T[] version call the IEnumerable variant.

The implementation detail could still be that an array is made at the call site and sent to the method, the benefit is to simply avoid having to create the extra overload.

@svick
Copy link
Contributor

svick commented Jan 19, 2015

This feature was already proposed for C# 6, but didn't make it due to scheduling. So my guess is that it's pretty likely it will make it into C# 7.

@jaredpar
Copy link
Member

If we are going to invest in a new params type I would like to see us invest in one that is more efficient than either T[] or IEnumerable<T> but equally inclusive.

Today low level APIs which expose a params method typically end up exposing several other non-params overloads in order to avoid call site allocation of the array for small numbers of arguments. It's not uncommon at all to see the following:

WriteLine(params object[] args)
WriteLine(object arg)
WriteLine(object arg1, object arg2)

...

// The above pattern lets the following bind without the object[] allocation
WriteLine("dog");

Switching to IEnumerable<T> would not help here and would in fact make it works because it creates two allocations for a small number of arguments:

  • The collection allocation at the call site
  • The `IEnumerator allocation in the caller

An idea we've sketched around a few times is a struct of the following nature:

struct Arguments<T> : IEnumerable<T>
{
  T _arg1; 
  T _arg2;
  IEnumerable<T> _enumerable;
  int _count;

  // struct based enumerator 
  // index which intelligently switches between T fields and enumerable
}

WriteLine(params Arguments<T> args)

The Arguments<T> struct represents a collection of arguments. In can be built off of individual arguments or a collection. In the case of a small number of individual arguments, which for many APIs is the predominant use case, it can do so allocation free. The compiler would be responsible for picking the best method of constructing Arguments<T> at the callsite.

@sharwell
Copy link
Member

@jaredpar It sounds like you want to use the calli and arglist IL instructions in C# code...

I could see a signature like this working pretty well:

WriteLine(params ArgIterator args)

@jaredpar
Copy link
Member

@sharwell yes and no. Those definitely achieve part of the problem: efficient way of calling a method with an arbitrary number of arguments. It doesn't hit the other part though which is easy interaction with .Net collections.

@sharwell
Copy link
Member

I actually see your concern as a separate issue from params IEnumerable<T>. This topic (IEnumerable<T>) is about exposing a convenient method for simplified APIs, where memory allocation characteristics are often (but not always) much less of a concern.

@HaloFour
Copy link

Having yet another separate class to represent params defeats most of the purpose of this feature which is to allow an API to expose a method that accepts IEnumerable<T> and support using params without having to explicitly write two separate methods.

Also, the performance of using foreach over an IEnumerable<T> which happens to be an instance of T[] is quite efficient, generally on par (if not faster) than using for, as unintuitive as that sounds.

@jaredpar
Copy link
Member

@HaloFour @sharwell

The intent of (params Arguments<T>) is to accept both individual arguments and IEnumerable<T> values equally well. There would be no need for (params IEnumerable<T>) in this scenario because it would already be covered.

void Method(params Arguments<int> args) { ... } 

void Example(IEnumerable<int> e) 
{
  Method(42);  // binds to Method(new Arguments<int>(42))
  Method(e);    // binds to Method(new Arguments<int>(e);
}

@HaloFour
Copy link

@jaredpar

I'm still not seeing any advantage to having an Arguments intermediary. If an API has specific optimized paths for dealing with a small number of arguments then even with Arguments they would have to be coded separately anyway so why is Arguments an improvement over having the overloads? The separate overloaded methods already provide the appropriate separation for the different algorithms and passing the arguments as individual values on the stack is more efficient than copying those values into a struct and copying that struct. If the API doesn't have specialized paths and will just enumerate the arguments via for or foreach then there is no performance benefit to this struct, even with 2 or fewer arguments.

@jaredpar
Copy link
Member

@HaloFour

The advantage is simply avoiding the allocation for the collection at the call site. It's not about specific optimizations within the method. The allocation may seem small, and typically is in isolated scenarios. Added up over an entire application though the array allocations add up (and quite fast).

I've worked on several projects where we've measured significant performance improvements by adding a few overloads to params methods that avoid call site allocations. The implementation of the non-params overload had no special optimizations. The algorithms, minus the loop, were identical to the params overload.

This is why I don't see the value in adding params IEnumerable<T>. It is solving only one of the current issues with params (not being inclusive to all collection types). I'd much rather invest in a solution that solves all of them. A solution like Arguments<T> has the possibility of doing so because:

  • It avoids the call site allocation in the target cases.
  • It can accept IEnumerable<T> as input (which in turn includes T[]).
  • The compiler can easily hide the implementation details in the same way it does for params T[] methods today.

@HaloFour
Copy link

The algorithms, minus the loop, were identical to the params overload.

Which is a different algorithm by definition since you're treating the arguments differently. You're better off keeping the specialized overloads since bouncing through a struct intermediary will be slower, even if you reference the fields containing the values directly. If you have to instead bounce through an indexer it will be significantly slower, on par with the speed of just working with an array.

The purpose of supporting params IEnumerable<T> is for the cases where performance isn't important because the performance will absolutely be slower even compared to even enumerating over params T[]. It's simply to eliminate the need to write that additional overload as this has become a common pattern:

public void Foo(params int[] args) {
   Foo((IEnumerable<T>)args);
}

public void Foo(IEnumerable<int> args) {
   // enumerate here
}

For those cases where the performance is important and you can behave differently given a small number of arguments the compiler story is already quite good through overload resolution. I see no need to complicate that and force a single code path which couldn't be a single code path anyway.

@jaredpar
Copy link
Member

You're better off keeping the specialized overloads since bouncing through a struct intermediary will be slower, even if you reference the fields containing the values directly

Completely disagree. We've tested out solutions in the past and found that the allocation benefit dominates any sort of indirection you would get from the struct.

Note: IEnumerable<T> adds even more indirection than T[] given that it will be

  1. An extra allocation.
  2. Double interface dispatch for every element in the loop.

It's simply to eliminate the need to write that additional overload as this has become a common pattern:

The Arguments<T> solution would fix the exact same scenario. It can accept individual arguments and IEnumerable<T> values.

Why push for a solution that is slower and allocates more memory over one which is solves the same problem + additional scenarios?

@HaloFour
Copy link

Why push for a solution that is slower and allocates more memory over one which is solves the same problem + additional scenarios?

Because those additional scenarios don't benefit without adding onus onto the developer of the method to write even more code than they need to today.

The following is slower than params T[]:

void Foo(params Arguments<int> args) {
    for (int i = 0; i < args.Count; i++) {
        int arg = args[i];
        // do stuff here
    }
}

And the following is slower than overloads:

void Foo(params Arguments<int> args) {
    if (args.Count == 2) {
        // assuming accessible public readonly fields, using indexers here is significantly slower
        int x = args.arg1;
        int y = args.arg2;
    }
    // need to handle other possibilities here as well
}

The one place where this could be nominally faster is in the case of params IEnumerable<T> where optimized IEnumerable<T> implementations can be provided for specific argument counts, which is something that the C# team could do when consuming params IEnumerable<T> rather than just emitting an array.

@jaredpar
Copy link
Member

@HaloFour the Arguments<T> type implements IEnumerable<T> using a struct based enumerator. I would expect the vast majority of consumers to use a foreach loop.

edit typo

@jaredpar
Copy link
Member

@HaloFour talk about the bad time for typos. I meant to say the exact opposite of that :( Editted the comment.

@HaloFour
Copy link

I would expect the vast majority of consumers to use a foreach loop.

I agree, and in those cases I do think that it would be worthwhile for the C# team to emit specialized implementations of IEnumerable<T> and IEnumerator<T> to improve performance, but that is a detail that could be hidden. The one gotcha there is that if the method is written to also check to see if the IEnumerable<T> is an T[] as that would not be true.

@mikedn
Copy link

mikedn commented Jan 19, 2015

Hmm, and if you want more than 2 arguments what happens? Will the compiler go and create an array and pass it to Arguments<T>? And if you need arguments of different types and they're value types you still end up allocating due to boxing, the only T you can use in such cases is Object. I'm a bit of a performance freak myself but this particular optimization attempt seems a bit overdone.

And I can't help not to notice that the allocation cost problem comes up quite often and every time we end up with solutions that are either incomplete or have some other not so great effects. Back in .NET 2.0 generic collections got struct enumerators, great, allocations avoided. And then you look at the JIT generated code and go hrmm. It looks like RyuJIT will produce better code but it took "only" 10 years.

Maybe at some point we need to accept that the system works the way it works and it has certain performance characteristics. If you want to make it better, well, use .NET Native, add escape analysis and stack allocation to it and call it a day.

Just my 2 cents.

@jaredpar
Copy link
Member

@mikedn

Hmm, and if you want more than 2 arguments what happens? Will the compiler go and create an array and pass it to Arguments?

Yes.

And if you need arguments of different types and they're value types you still end up allocating due to boxing, the only T you can use in such cases is Object

Correct but this is not a new problem. It already happens today with params.

It is definitely something I would love to see solved. So far though an elegant solution hasn't presented itself. If it did though I would likely be very interested in that as well.

Back in .NET 2.0 generic collections got struct enumerators, great, allocations avoided.

Struct based enumerators have been around since 1.0. It was the original way to have type safe, non-allocation enumeration. I do share the frustrations on enumerators though and I've written some thoughts about it here.

http://blog.paranoidcoding.com/2014/08/19/rethinking-enumerable.html

Maybe at some point we need to accept that the system works the way it works and it has certain performance characteristics.

Speaking as someone who's worked on a lot of perf sensitive applications over the years: allocations matter much more than most developers give them credit for. Most performance investigations end up doing little more than trying to reduce the GC time which translates into curbing unnecessary allocations.

Any time we create a feature in the language that has unnecessary allocations, it's a feature that will likely be avoided by perf sensitive applications. I'd much rather focus on features that are applicable to all types of programs.

@HaloFour
Copy link

@jaredpar

I do like elements of your rethinking on IEnumerable<T>. Without IEnumerator<T> don't you lose the capacity for generic variance? Also, and probably a fairly minor tweak, I'd also prefer TEnumerator to have a generic constraint of IDisposable, although I guess the compiler could just emit a try/finally which would check to see if TEnumerator was disposable and, if so, call Dispose(). Variance aside I love the idea of current being an out parameter. TEnumerator being a ref seems a tad weird but I get why you do it.

In the end, though, I think I'd rather be stuck with one slightly-less-perfect method than have a bunch of disparate but similar methods. IEnumerable<T> is still better than Java's Iterable<T> or Enumeration<T>.

@mburbea
Copy link
Author

mburbea commented Jan 19, 2015

@HaloFour , But at the same point, unless you are writing some very high specialized code like LINQ where you have optimized paths for different type of IEnumerable<T>, its very uncommon to actually inspect the underlying type of an IEnumerable<T>.

@mikedn, But boxing is always the problem if you have incompatible types. If you need different types and presumably need different handling for said type, you have no choice but to box. As for the rest of your points I'm not sure.

I like that @jaredpar 's idea as it help solves the extra overloads problem that my original suggestion was getting at.
e.g.

void SomeMethod(T item1, T item2)
void SomeMethod(params T[] items)
void SomeMethod(IEnumerable<T> items)

Under Jared's model this would be one method with one implementation. I also agree that IDisposable is very useful on enumerators. Especially those generated from yield return. Sometime s you want to tie resources lifecycle to iteration.
I think TryGetNext isn't bad but I'd probably want to still keep the abstraction of a Enumerator.

@HaloFour
Copy link

@mburbea Just supporting params IEnumerable<T> you also solve the additional overloads "problem" and can have one method with one implementation. You could then opt-into the additional overloads to provide optimized scenarios for accepting arrays or specific numbers of arguments but only if you wanted to. If your one implementation is just going to be using foreach or LINQ then it doesn't really matter as you're using the most expensive implementation anyway. But if you know that you can have an optimized path given exactly one or two arguments then it always makes sense to have the overloads since that already works very well with overload resolution and provides the least expensive path for passing the values.

@mikedn
Copy link

mikedn commented Jan 19, 2015

@jaredpar Actually struct enumerators were added in 2.0, check ArrayList for example, it has a class enumerator and its GetEnumerator returns IEnumerator. And yes, I read your blogpost and I know you worked with Joe Duffy on a certain project 😄.

Oh well, I suppose it makes sense for the compiler and framework to strive to minimize allocations. The main problem is how will the indexer deal with IEnumerable. I suppose you'll end up with something like this, otherwise you'll end up enumerating multiple times:

T this[int index] {
    if (count > 2) {
        var list = _enumerable as IList<T>;
        if (list == null) {
            list = _enumerable.ToArray();
            _enumerable = _list;
        } 
        return list[index];
    }
    ...

As for boxing, that's likely unavoidable. You'd need something similar to C++'s variadic templates to get make it work.

@jaredpar
Copy link
Member

@mikedn guess you learn something new every day. I would have sworn struct enumerators were in 1.0. :)

@mburbea
Copy link
Author

mburbea commented Jan 20, 2015

@HaloFour , when I'm at the point where I'm taking variadic arguments I rarely will be doing much differently then with one argument or N arguments.e.g.DoSomething(arg1) vs foreach(var arg in args){ DoSomething(arg)} I'm after that easier calling convention. Perhaps I could avoid the loop, but this type of code usually isn't the bottle neck of performance for me.

I probably would use it like params IEnumerable<T> as long as Arguments implements IEnumerable<T> and the compiler would handle the conversion itself. Can the jit put structs like this into registers or is that a bridge too far?

@mikedn
Copy link

mikedn commented Jan 20, 2015

The JIT may place a struct in a register if it contains only one field that fits in a register (a field of type int for example) so Arguments has no chance of being passed in registers. Anyway, x86 and even x64 have too few registers for this to be useful.

@HaloFour
Copy link

@mburbea In which case you'd likely not be writing those additional overloads anyway as they would serve no purpose for you and having an intermediate struct would be of no benefit, either syntactically or performance-wise.

I believe where you would see the improvement of an intermediate struct would be to have the compiler emit such a struct as the IEnumerable<T> rather than just creating an T[] as the former can be more optimized than the default array enumeration.

@mikedn
Copy link

mikedn commented Jan 20, 2015

@HaloFour What do you mean by the former can be more optimized than the default array enumeration? Array enumeration is as fast as it gets, there's nothing more optimized than that. Maybe you meant the opposite or I did I read it wrong?

@mburbea
Copy link
Author

mburbea commented Jan 20, 2015

I think he means array enumerators aren't the fastest. e.g.

void SomeFunc(T[] args){
   foreach(var a in args){ DoSomething(a)};
}

will actually get compiled as a for loop to avoid creating an enumerator. If you instead change the signature to IEnumerable<T> it'll actually be slower than if you past it a List<T>. This has something to do with the SzArrayHelper jit stuff last I read about it on stackoverflow.
It's too late now but is there any reason that the array enumerator isn't a struct one?

@SergeyZhurikhin
Copy link

I would like to:

public void Foo1(params T[] args) { ... }
public void Foo2(params IEnumerable<T> args) { ... }
public void Foo3(params IReadOnlyCollection<T> args) { ... }
public void Foo4(params IReadOnlyList<T> args) { ... }

Method calls:

FooX(a, b, c);

is translated as:

FooX(new T[] { a, b, c });

@HaloFour
Copy link

@SergeyZhurikhin

Might as well include IList<T> and ICollection<T> as well to complete the list of generic interfaces implemented by a single-dimension array, no?

@SergeyZhurikhin
Copy link

@HaloFour
Absolutely not!
For example:

public void Foo4(params IList<T> args)  { args.Add(t); ... }

@HaloFour
Copy link

That would be a runtime error, just like it would be today if you passed an array manually.

@SergeyZhurikhin
Copy link

@HaloFour
So I'm doing it a compile-time error.

@alrz
Copy link
Member

alrz commented Mar 26, 2016

I agree with @SergeyZhurikhin. A method getting an IList<T> is likely to call Add, so it is not a good idea to throw exceptions with normal uasge of a language feature.

@HaloFour
Copy link

@alrz

Any such method is already taking that risk if they don't first check the IsReadOnly property. The IList interface makes no claim that the implementation is writable. Arrays have always implemented these interfaces.

This would also provide an option to pass an interface of an indexable list with params when targeting a framework older than 4.5.

Not that I care all that much either way.

@SergeyZhurikhin
Copy link

@HaloFour, @alrz

public void Foo4(params IList<T> args)  { args[1] = 5; ... }
...
Foo4(1, 2, 3);           // - Tolerated!

var d = new[] { 1, 2, 3 };
Foo4(d);
>Print(d)        // int[3] { 1, 5, 3 }   - Nightmare!!

@HaloFour
Copy link

HaloFour commented Apr 4, 2016

@SergeyZhurikhin

That's already perfectly legal with params:

public void Foo4(params int[] args) {
    args[1] = 5;
}

...

var d = new [] { 1, 2, 3 };
Foo4(d);
Debug.Assert(d[1] == 5);

@SergeyZhurikhin
Copy link

@HaloFour
Legally - not to help not make mistakes, do not build their reefs.

@Pzixel
Copy link

Pzixel commented Jun 20, 2016

@HaloFour

Any such method is already taking that risk if they don't first check the IsReadOnly property. The IList interface makes no claim that the implementation is writable. Arrays have always implemented these interfaces.

And this is one of my least-favorite feature in BCL. Standad collection hierarchy is so ugly, and it seems that it will be as it is forever, becuase backward compatibility is the holy cow for Microsoft. It's bizzare, that array implements IList, and half of methods throws NotAllowedException. Is it statically typed language or not?

@aluanhaddad
Copy link

@Pzixel compatibility is actually a really important feature but that's another topic.

The good news is that we as developers have access to modern, typesafe and well specified interfaces such as IEnumerable<T> and IReadOnlyList<T>.

@Pzixel
Copy link

Pzixel commented Jun 20, 2016

@aluanhaddad problem with IEnumerable us that it can be a query, which implies multiple consequences.

IReadOnlyList<T> is good enough, but sometimes I need to modify something within.

Proper collection hierarchy was posed several years ago on SO:

  • Just Enumeration IEnumerable
  • Readonly but no indexer (.Count, .Contains,...)
  • Resizable but no indexer, i.e. set like (Add, Remove,...) current ICollection
  • Readonly with indexer (indexer, indexof,...)
  • Constant size with indexer (indexer with a setter)
  • Variable size with indexer (Insert,...) current IList

Becuase now if I want to create my own collection with Count property, I should provide multiple methods which I really do not need, I just want to tell to user hey, this is materialized collection with N elements, don't worry about multiple query executions.

IMHO BCL team should provide new hierarchy of interfaces, well elaborated, and insert it in current hierarchy. It won't break anything, we'l just have new interfaces (for example like it's showed above).
For example, split ICollection<T> on ICommonReadonlyCollection<T> and ICommonCollection<T>, and then ICollection<T> : ICommonReadonlyCollection<T>, ICommonCollection<T> (names are just for example).

So we'l have compatibility with much better hierarchy. But it seems that BCL team don't think that current situation is bad in any sense. But it obviously is, because of properties IsReadOnly and IsFixedSize. Why should I check those before interacting with collection? Where is polymorphism? If method could be invalid in some situation then interface shouldn't contain it.

@Thaina
Copy link

Thaina commented Jun 27, 2016

Just asking, is this would support generic params?

I mean something like this

void Iter<E,T>(params E collections) where E : IEnumerable<T>
{
    // iterate T
}

@Pzixel
Copy link

Pzixel commented Jun 27, 2016

@Thaina you can replace IEnumerable with T[] and see if it works. I'm sure that it doesn't.

@aluanhaddad
Copy link

aluanhaddad commented Jun 29, 2016

@Pzixel so you just want an interface with a Count property. It will need to extend IEnumerable<T> to be useful in most situations, so it's really just IReadonlyList<T> without the indexer. It doesn't seem like much of a problem. All that would need to be done would be to extract that interface into the bcl.

@Pzixel
Copy link

Pzixel commented Jun 29, 2016

@aluanhaddad hmm, we have such interface, it's a ReadOnlyCollection<T>, but here is a problem that its name do not reflect its purpose. If collection has a property count it doesn't mean that it's readonly. But at least we have it...

@BrainSlugs83
Copy link

BrainSlugs83 commented Jul 13, 2016

@jaredpar I wanted to weigh in on your Arguments suggestion:

Is there any reason why you only have one Arguments struct? -- It seems like the intelligent indexer you suggest could be as much of a bottle neck as the problem you're trying to solve. I suggest making several versions of the struct, and letting the compiler chose which one makes the most sense at compile time -- excuse the poor naming, but an example of the compiler decision could be:

  • 1 Argument => new Argument1(arg0);
  • 2 Arguments => new Argument2(arg0, arg1);
  • 3 Arguments => new Argument3(arg0, arg1, arg2);
  • 4+ arguments => new ArgumentArray(new T[n] { arg0, arg1, arg2, ... argN });
  • Other IEnumerable, BCL collection, etc. => new ArgumentEnumerator(arg0);

(Supporting the last one is important, because for many of us, the whole point of this request is to avoid having two method signatures for params vs BCL collections.)

This will allow a smaller footprint too (the struct only stores the exact data that it needs, and only performs the exact logic it needs to in the intelligent indexers, etc.).

You could make an interface for the structs -- something like IArguments -- that way we could just have our signatures as "params IArguments". (If IArguments won't work because of boxing or some such, consider using function pointers to hook up the "intelligent" logic in the struct to emulate OOP behavior).

Personally, I would prefer that our method signatures be allowed to use IEnumerable if we wish, for methods that don't require the use of indexers (the same exact compiler logic would work, because [I?]Arguments inherits IEnumerable) -- and it will allow us to mark methods as being able to accept object streams (i.e. long running "yield return" jobs, etc.).

@HaloFour
Copy link

@BrainSlugs83

The point of the single Arguments struct is avoiding having to hide them behind an interface. Doing so requires boxing the struct and performing virtual dispatch which would eliminate the performance benefit of going with an Arguments struct in the first place.

@BrainSlugs83
Copy link

@HaloFour I believe I addressed the boxing issue in my comment.

@HaloFour
Copy link

@BrainSlugs83

(If IArguments won't work because of boxing or some such, consider using function pointers to hook up the "intelligent" logic in the struct to emulate OOP behavior)

Care expanding that into something that applies to the CLR? IIRC, the only way you could do this would be to force the method to be generic where the params argument is of a generic type parameter constrained to IArguments and struct, which would allow for the IL modifier constrained. which would permit calling the members without boxing. But that would change the contract of the method.

@BrainSlugs83
Copy link

BrainSlugs83 commented Jul 13, 2016

@HaloFour sure; it's a pretty common thing though (in non-OOP languages -- the idea is you at least get to avoid the speed/complexity hit of determining which block of code to run at runtime).

Here's some pseduo code in the context of the original struct:

public struct Arguments<T> : IEnumerable<T>
{
  // All values assigned at creation time.
  private T _arg1; 
  private T _arg2;
  private IEnumerable<T> _enumerable;

  private Func<int, T> _getter;
  private Func<int> _count;

  public int Count { get { return _count(); } }

  public T this[int idx]
  {
    get { return _getter(idx); }
  }

  public Arguments<T>(T arg)
  {
      _arg1 = arg;
      _count = 1;
      _getter = _getSingle; // method not shown for brevity
      _count = ()=> 1;
  }

  public Arguments<T>(T arg1, T arg2)
  {
      _arg1 = arg1;
      _arg2 = arg2;
      _getter = _getDouble; // method not shown for brevity
      _count = ()=> 2;
  }

  public Arguments<T>(IEnumerable<T> args)
  {
      _enumerable = args;
      _getter = _getEnumerable // method not shown for brevity
      _count = ()=>_enumerable.Count() ; // avoids iterating until necessary 
  }
}

@HaloFour
Copy link

@BrainSlugs83

Adding delegates to the mix would only add to the overhead since delegate invocation is not cheap. That still doesn't solve the issue of what you'd be passing to this method. At best you'd pass the two delegates and incur the penalty of a n+1 delegate invocations. But that would mean that you're no longer passing a single params argument and the developer would have to learn how to write methods that accept these delegates. Or the method would accept an interface containing those two delegates, which brings us right back to the boxing issue.

Given that the entire point of Arguments<T> is to avoid the overhead associated with boxing and dispatch going with delegates is completely pointless. The cost of copying a struct that happens to be larger than it needs to be would be significantly less.

@gafter
Copy link
Member

gafter commented Mar 20, 2017

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

This request corresponds to dotnet/csharplang#179

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests