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

Proposal: in-set and in-range operator to ease checking of value within a pre-defined set #420

Open
sirgru opened this issue Apr 8, 2017 · 14 comments

Comments

@sirgru
Copy link

sirgru commented Apr 8, 2017

Sometimes when checking if a variable falls within a pre-define set of values there can be a lot of verbosity:

if(someVariable == 'a' || someVariable == 'b' || someVariable == 'c') {}

it would be nice if this could be shorthanded to something like:

if(someVariabe in set {'a', 'b', 'c'}) {}

There are, of course, many ways of going about this already, such as IEnumerable.Contains, but sometimes we don't want to create sequences.

Also, sometimes we want to check for a variable within range:

if(someVariable >=1 && someVariable <= 5) {}

this could be shortened to:

if(someVariable in set {1..5}) {}

These would of course work on anything that is IComparable, as above would for anything that is IEquatable. It would simply be syntactic sugar.
The argument can be made about inclusion and exclusion of the last element in range, same as with Ranges. That part of syntax would potentially be the same in both places.

The difference between this and ranges is that Ranges would be a data type and this would be only syntactic sugar. The goal here is not to create another array and enumerate over it.

For example, the example in related #330 would look like:

#Before
if (t.State == States.On || t.State == States.InBetween)
#After
if(t.State in set {States.On, Statess.InBetween})

Related, but not the same: #316 . Difference: in without a set already assumes a sequence follows. Having set here serves to explicitly denote a finite set which is translated into individual checks.

@svick
Copy link
Contributor

svick commented Apr 8, 2017

I don't think I like the in set syntax, it's too much of a special case for new syntax to be added just for that.

For the sequence case, what about this?

if (('a', 'b', 'c').Contains(someVariable))

It's short, efficient, fairly consistent with the existing LINQ syntax, and requires only a adding an extension method (so you could even implement it yourself).

For the range case, what makes syntactic sugar better than creating a Range type?

@sirgru
Copy link
Author

sirgru commented Apr 8, 2017

@svick The problem is solvable with extension methods, but iterating over a sequence may not be desirable because of overheads of involving LINQ in these simplest cases. With the proposed solution, it wouldn't boil down to iterators but to a bunch of inline if == checks.
With ranges the difference is even greater. It would boil down to comparing <= >= on the border of the values, and the range of values wouldn't be involved. The range-like representation is just for readability.

I have seen a couple of similar syntax ideas floating around, and it seems all of them rely on some kind of special case thinking. e.g. if(a is in {'a', 'b'}).

@svick
Copy link
Contributor

svick commented Apr 8, 2017

iterating over a sequence may not be desirable because of overheads of involving LINQ in these simplest cases

I guess I wasn't clear: my suggestion was to use a tuple. "Iterating" over that should not have any significant overhead: no iterators, no allocations, no virtual calls (at least for value types, I think).

The implementation could look like this:

static bool Contains<T>(this (T, T, T) tuple, T value) =>
    Equals(value, tuple.Item1) ||
    Equals(value, tuple.Item2) ||
    Equals(value, tuple.Item3);

(Obviously you would also need overloads for number of items other than three.)

It would boil down to comparing <= >= on the border of the values, and the range of values wouldn't be involved.

That's exactly what a Range type (which currently does not exist) could do too.

@sirgru
Copy link
Author

sirgru commented Apr 8, 2017

@svick Ah, OK. I didn't think of the tuples solution.

I was presuming that writing in range would cause the range to be iterated over, but of course it is too early to speculate about that.

@nesteruk
Copy link

nesteruk commented Apr 8, 2017

public static bool IsOneOf(this T self, params T[] items)
{
  return items.Contains(self);
}

Usage:

if (operation.IsOneOf("OR", "AND", "XOR")) { ... }

@wanton7
Copy link

wanton7 commented Apr 9, 2017

@nesteruk with current c# compiler and jit method with params always allocates an array when you call it. So using params is a bad bad idea.

@nesteruk
Copy link

nesteruk commented Apr 9, 2017

@wanton7 I am 100% OK with the compiler allocating an array here.

@jnm2
Copy link
Contributor

jnm2 commented Apr 9, 2017

@wanton7 So what you do is the same thing you do for string.Format: offer more specific overloads with the most common number of parameters.

@wanton7
Copy link

wanton7 commented Apr 9, 2017

@jnm2 that would work.

@DavidArno
Copy link

DavidArno commented Apr 12, 2017

This is effectively a duplicate of #316. And, just like with that proposal, such requests are examples of use cases that could be covered by active patterns. The active pattern proposal covers these scenarios and many more:

public bool InPattern<T>(this IEnumerable<T> collection, T matchValue) =>
    collection.Contains(matchValue);

public bool InRangePattern<T>(this T value, T minValue, T maxValue) where T : IComparable =>
    value.CompareTo(minValue) >= 0 && value.CompareTo(maxValue) <= 0;
...
if(someVariable is In('a', 'b', 'c')) {}

if(someVariable is InRange(1..5)) {}

@BalassaMarton
Copy link

Maybe I'm missing some parsing aspect, but what's wrong with simply writing this:

if (x in (1, 2, 3)) {}

which would be lowered to

if (x == 1 || x == 2 || x == 3) {}

?

@BalassaMarton
Copy link

#4108 would cover ranges perfectly:

if (1 < x <= 9) {}

@jnm2
Copy link
Contributor

jnm2 commented May 26, 2021

In C# 9, you can do if (x is 1 or 2 or 3) {.

@GitClickOk
Copy link

Can Collection Expressions enable something like this?

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

11 participants