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

Proposal: Params everything #1333

Open
ghord opened this Issue Feb 21, 2018 · 16 comments

Comments

Projects
None yet
10 participants
@ghord

ghord commented Feb 21, 2018

Summary

Allow arbitrary object types to be marked as params in method declarations to allow optional parameter reuse.

Motivation

In API's with multiple optional parameters, maintaining multiple method overloads that take the same optional parameters is a pain. Overriding those methods is even more pain.

Optional parameters don't allow for non constant default values and you often have to give different meaning to default values.

Detailed design

When using params keyword, passing any class/struct that has [ParamsAttribute] should be allowed. Classes with [ParamsAttribute] are instantiated with default constructor and their properties are filled with values passed to method as optional parameters.

I.E.

[Params]
public class Foo
{
    public int? bar { get; set; } 
    public int? bar2 { get; set; }
}

void MyMethod(params Foo foo)
{
    ...
}

MyMethod(bar2: 12);

Gets translated to

var param = new Foo { bar2 = 12 };
MyMethod(param);

[ParamsAttribute] is used to avoid ambiguities with existing and future uses for params keyword (IEnumerable<T>/ICollection<T>/Span<T>/IDictionary<string, object> ).

structs param could be useful for avoiding allocations combined with in modifier on params parameter.

Params could be generic parameter to allow for uses unforseen by the author of proposal ;)

@jnm2

This comment has been minimized.

Contributor

jnm2 commented Feb 21, 2018

This is a fairly interesting idea that could make make scenarios like DI easier. dotnet/roslyn#16745, aspnet/EntityFrameworkCore#7465 (comment)

I'd think you'd want to restrict it to structs. That way there's always a default constructor, you don't force a heap allocation, the perf is the same as if you had passed each parameter individually, you don't have to worry about null reference exceptions, and it forces composition instead of inheritance.

Seems like you'd want a separate attribute type though.

@jnm2

This comment has been minimized.

Contributor

jnm2 commented Feb 21, 2018

Also, I'd prefer to restrict it to fields; no properties. It's not like it would be legitimate to allow properties to disguise logic as simple parameter assignments, would it?

@Happypig375

This comment has been minimized.

Happypig375 commented Feb 21, 2018

  1. What happens for void MyMethod(int? bar, params Foo foo) and MyMethod(bar: 12)?
  2. How will collection types be used with this?
[Params]
class MyList : IList<int> { ... }
void MyMethod(params MyList list) { ... }
MyMethod(1, 2, 3); //Is this allowed?
  1. Will param types be allowed in other param types? If so, what will happen for the following:
[Params]
public class Foo
{
    public Bar bar { get; set; }
}

[Params]
public class Bar
{
    public int? foobar { get; set; }
}

void MyMethod(params Foo foo) { ... }

MyMethod(foobar: 3) //Will this work?
  1. Will each field be forced to be nullable? Or will default values be given to fields with non-nullable types?
@Happypig375

This comment has been minimized.

Happypig375 commented Feb 21, 2018

Also, please fix your compiler translation. It should be:

var param = new Foo { bar2 = 12 };
MyMethod(param);

Note bar2 instead of bar.

@HaloFour

This comment has been minimized.

Contributor

HaloFour commented Feb 21, 2018

The keyword params means a variable number of arguments. This proposal would radically change that. I don't see much point to this proposal at all. It's at best minor syntax candy which hides a great deal of detail and requires/encourages authoring types that clash with the normal .NET naming guidelines. Why is that better than just writing new() { Bar2 = 1 } and actually passing the type?

@jnm2

The DI angle might be interesting, but that seems like something very easily solved by the DI container itself. I'd been using an extension providing support for this in Ninject some 8 years ago.

@jnm2

This comment has been minimized.

Contributor

jnm2 commented Feb 21, 2018

@HaloFour That's cool! Can I see what it looks like?

@HaloFour

This comment has been minimized.

Contributor

HaloFour commented Feb 21, 2018

@jnm2

It's been a while but I recall it working through writing an interface that exposed the dependencies as properties, and then taking a dependency on that interface. The extension would create an implementation of that interface.

@ghord

This comment has been minimized.

ghord commented Feb 21, 2018

The idea is borrowed from javascript, where functions often take anonymous object with different options which works quite nicely. With this proposal, C# could have something like that but type safe.

The main advantage of this is the ability to reuse parameter group multiple times (different overloads etc) and make it look like you are calling normal methods. Creating options class for each call is also fine, if more cumbersome (at least with lack of inferred new()).

Breaking naming guidelines already happens with tuples, so this doesn't seem like such a big issue.

@Happypig375 All those questions are interesting ones. My first thoughts are:

  1. optional parameter always has priority (avoid issues with different param types in params struct/class).
  2. No. It's not allowed right now and I think it should stay that way.
  3. Probably not (?). Potentially could use inheritence instead.
  4. No forcing to nullable. Default values should be enough - for reference types Optional could be used when null value passing is desired.

@jnm2 Excellent points, I agree on both counts.

@Happypig375

This comment has been minimized.

Happypig375 commented Feb 22, 2018

If you agree on restricting param types to structs (first point of @jnm2), then you cannot use inheritance (answer to my third question) 😉

@aluanhaddad

This comment has been minimized.

aluanhaddad commented Feb 22, 2018

@ghord

The idea is borrowed from javascript, where functions often take anonymous object with different options which works quite nicely. With this proposal, C# could have something like that but type safe.

The main advantage of this is the ability to reuse parameter group multiple times (different overloads etc) and make it look like you are calling normal methods. Creating options class for each call is also fine, if more cumbersome (at least with lack of inferred new()).

It is indeed very useful in JavaScript. But I do not see what this has to do with varargs.

It would be incredible to have structural typing capabilities in C# but they shouldn't be tied to parameter lists. As you know from JavaScript, being able to return such an ad hoc object is equally important.

@bondsbw

This comment has been minimized.

bondsbw commented Feb 22, 2018

I think it would need a different syntax to be unambiguous. Say

MyMethod(12 to bar2);
@jnm2

This comment has been minimized.

Contributor

jnm2 commented Feb 22, 2018

@bondsbw Sure you can't use a .To extension method for that? 😂

@JesperTreetop

This comment has been minimized.

JesperTreetop commented Feb 26, 2018

Am I wrong in thinking this is more or less a form of splatting? No matter what, I highly approve but would propose using a different keyword for this. It would also allow objects taking lone T[] parameters without a conflict (in case ParamsAttribute opts into a whole new model, it would at least prevent the mental ambiguity).

@Richiban

This comment has been minimized.

Richiban commented Feb 26, 2018

In a somewhat related feature, it looks like F# is planning on adding params dictionary arguments (fsharp/fslang-suggestions#260). If C# had this, you would be able to write:

public void M(params Dictionary<string, string> args) 
{
    ...
}

And then call it like this:

M(a: "one", b: "two", c: "three");

Which would be the equivalent of this, in current C#:

M(new Dictionary<string, string> { { "a", "one" }, { "b", "two" }, { "c", "three" } });
@AustinBryan

This comment has been minimized.

AustinBryan commented Jun 8, 2018

I agree with @HaloFour, it took me quite a while to realize that Foo wasn't going to be an array or other IEnumerable.

@alrz

This comment has been minimized.

Contributor

alrz commented Jun 13, 2018

In my opinion, this would be an exotic feature. I'd rather have dictionary literals for this.

Target-typed new would help here but it does not contribute to type inference like so.

void M<K, V>(Dictionary<K, V> map) {}

M(new() { {"a", "one"} }); // error as currently specified
M(["a": "one"]); // I'd expect this to compile

I took a note to bring this up for discussion. I wonder if that would worth it, considering that "dictionary literals" is already a possibility (#414).

PS: As @HaloFour said, you can use new() { a = "one" } if you actually have a type, which seems good enough to me - as long as you don't have any other eligible overloads for new().

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