77

Instead of

if (foo == "1" || foo == "5" || foo == "9" ... ) 

I like to combine them similar to the following (which doesn't work):

if (foo == ("1" || "5" || "9" ... ))

Is that possible?

Mike Perrenoud
  • 66,820
  • 29
  • 157
  • 232
KMC
  • 19,548
  • 58
  • 164
  • 253
  • 1
    Just as a related aside, Perl 6 is supposed to have these (`if (foo == any(1, 5, 9))`), and Perl 5 code can use them too using CPAN modules http://search.cpan.org/perldoc?Quantum%3A%3ASuperpositions or http://search.cpan.org/perldoc?Perl6%3A%3AJunction This any() and all() functions are called 'junctions', and I love the idea :) – Sundar R Jul 23 '13 at 21:09
  • Do yuo have dicts in `c#`, we have in Python so we can do: `{"1": 1, "5": 1, "9": 1, .... }.gets(foo, false);` its `O(1)`. --- `gets()` is a dict's function that index `foo` key in dict and give value, else second arguments=false as default. .. I am not a c# user I came from hot question. – Grijesh Chauhan Jul 23 '13 at 21:28
  • 4
    Another side note, python uses `foo in ("1", "5", "9")` which I think is less ambigous. If c# has a terse literal array syntax, perhaps the analogue would be `["1", "5", "9"].index(foo) != -1` – tobyodavies Jul 23 '13 at 22:47
  • Won't work in all situations, so I won't post this as an answer, but for your specific example: `if (int.Parse(foo) % 4 == 1)` will do the trick as a good simple one-liner. Assumes in this case that `foo` will always be a single-digit parse-able int. Not knowing your intended use case I can't tell if that's what you'd need. – Darrel Hoffman Jul 24 '13 at 03:05
  • Yeah, I was [thinking][1] it was an annoyance, too.... [1]: http://programmers.stackexchange.com/questions/205165/operator-distributivity-in-expressions-syntactic-sugar – Michael Rosefield Jul 24 '13 at 08:12
  • in PERL you don't really need any function, you can just do a regex `if(foo=~/^(1|5|9)$/)` – Naryl Jul 24 '13 at 09:55
  • Since we're mentioning different languages, Ruby does this easily: `["1", "5", "9"].include? foo`. Or Haskell: `any (== foo) ["1", "5", "9"]`. – Malcolm Jul 24 '13 at 10:51
  • @sundar: Perl can also do `if ( grep { $foo eq $_ } (1, 5, 9) ) ...`. The resulting array evaluated as a scalar will evaluate 0 if empty (i.e., nothing matched) or the size of the array. – Blrfl Jul 24 '13 at 12:26
  • @DarrelHoffman That are not integers that are string – Trikaldarshiii Jul 24 '13 at 12:36
  • Similar: http://stackoverflow.com/questions/17378673/1075247 – AncientSwordRage Jul 24 '13 at 13:00
  • @Mohit That's why I used `int.Parse`. It would work for this very specific scenario, but if you passed in a string that wasn't parse-able as an int, it'd fail. (You could use `int.TryParse`, but it requires an extra line for validation.) Like I said, won't work in every case, but if you know certain things ahead of time it does. – Darrel Hoffman Jul 24 '13 at 14:55
  • 2
    @GrijeshChauhan: if you don't need the values, then using `set()` is generally going to be better than a `dict()` whose values are always 1. – Lie Ryan Jul 24 '13 at 15:08
  • Good I will try, I am new python learner ..Thanks! – Grijesh Chauhan Jul 24 '13 at 16:22
  • @DarrelHoffman is tryParse available for lower versions like 3.5 – Trikaldarshiii Jul 24 '13 at 17:23
  • @Mohit Pretty sure TryParse has always been available - at least as early as 2.0, which is the earliest that still seems to be covered on http://msdn.microsoft.com/en-us/library/system.int32.tryparse%28v=vs.80%29.aspx – Darrel Hoffman Jul 24 '13 at 17:30
  • @DarrelHoffman ya i think i mixed with TryParse of Enum which is now available from 4.0 but not earlier it my lack – Trikaldarshiii Jul 24 '13 at 19:11
  • @DarrelHoffman check my answer i think i have ultimate solution – Trikaldarshiii Jul 24 '13 at 19:42

8 Answers8

169

Unfortunately not, your best bet is to create an extension method

public static bool IsOneOf<T>(this T value, params T[] options)
{
    return options.Contains(value);
}

and you can use it like this:

if (foo.IsOneOf("1", "5", "9"))
{
    ...
}

Being generic, it can be used for any type (int, string etc).

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
  • 5
    If I had any more up votes for the day I'd give you a +1 friend. Good, clever solution. – Mike Perrenoud Jul 23 '13 at 16:37
  • 6
    It's the cleanest and most reusable of out all answers. – Zbigniew Jul 23 '13 at 16:38
  • 1
    This and more http://stackoverflow.com/questions/271398/what-are-your-favorite-extension-methods-for-c-codeplex-com-extensionoverflow – Tomasito Jul 23 '13 at 23:45
  • 1
    The || operator in OP's question is a short cut operator. Does the Contains extension method exit as soon as it finds a match? Or does it iterate through the whole array? If it does not, this is preferable to the switch..case because you can use non-constant expressions. – Boluc Papuccuoglu Jul 24 '13 at 10:23
  • I would assume that Array.Contains would return true as soon as it finds a match in the collection it would make sense that it did but you'd have to decompile the source of mscorlib to verify that. – Trevor Pilley Jul 24 '13 at 10:27
  • @TrevorPilley Not necessarily. Make arrays of many sizes starting with `1` and call Contains for `1`. If the call is O(1), then it stops when it finds a match. If it is O(N), then it checks the whole array. No need for decompilation. – luiscubal Jul 24 '13 at 14:12
  • I use this exact code, but I call my method "In" because the usage is very similar to SQL. – KeithS Jul 24 '13 at 14:56
  • 1
    Unless optimized at compile time (such as is possible if all the values are constants), creating the `params T[]` array is always going to be `O(N)` so it will not necessarily benefit from short-circuit. – Lie Ryan Jul 24 '13 at 15:11
  • You should name the method `IsAnyOf`. The terms `Any` and `All` are used in the .NET framework for similar situations, so the terms are familiar and prevent the user from ever wondering whether `IsOneOf` means `IsAnyOf` or `IsOnlyOneOf`. – Sam Harwell Jul 25 '13 at 02:51
  • Adding a null source check to the extension method would be good practice, as well. Could potentially save you from digging through some cryptic stack traces. – nw. Aug 06 '13 at 23:14
37

You cannot do it this way. Instead you can do this:

string[] validValues = new string[] { "1", "5", "9", "whatever" };
if(validValues.Contains(foo))
{
    // do something
}
Zbigniew
  • 27,184
  • 6
  • 59
  • 66
  • 4
    I'm not sure, but it appears somebody went through and down voted everybody's except for @Gray. – Mike Perrenoud Jul 23 '13 at 16:30
  • :( Wasn't me, I promise. I shall go and upvote all. – Gray Jul 23 '13 at 16:31
  • Then, as it appears, someone came in, and downvoted all (before Gray posted answer). Well, it's not the first time... – Zbigniew Jul 23 '13 at 16:34
  • 2
    not me, and I don't understand why my question was downvoted twice either.. – KMC Jul 23 '13 at 17:04
  • 5
    Some (not me!) might consider it basic or something that could have been answered by Google or elsewhere on the site. Or someone might just be a jerk. Or both. I gave you an upvote to undo the damage. Take that, anonymous jerk! – thumbtackthief Jul 23 '13 at 20:48
  • Unfortunately you just have to put up with a few idiotic jerks. The plus votes you are getting from people fed up with them should hopefully make up for it though! (+1) – Relaxing In Cyprus Jul 24 '13 at 08:26
  • Well, I certainly wasn't expecting to get +24 for this answer, it's accually pretty simple. On the other hand all my most upvoted answers are pretty much basic. Thanks! – Zbigniew Jul 24 '13 at 15:09
25

One possible option is this:

switch (foo)
{
    case "1":
    case "5":
    case "9":
        // your code here

        break;
}

Another possible option is this:

var vals = new string[] { "1", "5", "9" };
if (vals.Contains(foo))
{
    // your code here
}
Mike Perrenoud
  • 66,820
  • 29
  • 157
  • 232
23

If all options are just one character you could do:

if ("159".IndexOf(foo) != -1)
{
  //do something
}
jsedano
  • 4,088
  • 2
  • 20
  • 31
  • 1
    Hmm, interesting approach. One I've never considered. Nice. – Mike Perrenoud Jul 23 '13 at 16:38
  • 13
    And there goes the readability... – RedX Jul 24 '13 at 08:11
  • 4
    @user902383 Yes I know that, that's why I wrote: **If all options are just one character** on the top of my answer – jsedano Jul 24 '13 at 14:01
  • @RedX I'm all about readability, that's why I think Trevor Pilley answer is the best one, this is great **foo.IsOneOf("1", "5", "9")**, I think my answer /could/ hurt readability depending on the context. – jsedano Jul 24 '13 at 18:50
18

Here is yet another alternative:

bool x = new[] { "1", "5", "9" }.Any(a => a == "5"); //x == true
bool y = new[] { "1", "5", "9" }.Any(a => a == "8"); //y == false

It is better to use .Contains(foo) in this case, as the flexibility of the lambda is rather wasted here. If there was a complex expression that needed to be done, Any would be more useful.

Gray
  • 7,050
  • 2
  • 29
  • 52
  • 1
    Instead of `.Any(x => x == val)` I would rather use `.Contains(val)`. – fjdumont Jul 24 '13 at 10:31
  • I could see that. I think, for this kind of collection, it makes no difference (aside from syntax), but for certain collections, like say, a Binary Search Tree, `.Contains` would perform a binary search... where `.Any` would still go through in O(n). – Gray Jul 24 '13 at 12:29
  • ... which would be awesome, right? A binary search tree searches in O(log n) IIRC. – fjdumont Jul 24 '13 at 13:20
  • Right, that's what I was saying. This was a helpful discussion, though. I learned the real reason to use Any over Contains. You would only really use Any() over Contains if you need a specialized lamda (which I do not have in this case). Contains() is better (worst case: the same) as Any unless you need to check something a little more complex than if the value is equal. I'll leave my answer here just as a reference for future people. – Gray Jul 24 '13 at 13:29
13

You can do this, if that's acceptable to you:

if ( (new string[] {"1","9","5","6" }).Contains(foo))
{

}
Icarus
  • 63,293
  • 14
  • 100
  • 115
  • 1
    Does this allocate a new array every time it runs? – Samuel Edwin Ward Jul 23 '13 at 20:39
  • @SamuelEdwinWard: It looks like it should. I don't know if the current runtime does it but escape analysis on this line should allow it to instantly free the new array. – Zan Lynx Jul 23 '13 at 23:42
  • 1
    You can leave out `string`, the compiler will infer this automatically from the specified array values. – Sandor Drieënhuizen Jul 24 '13 at 11:35
  • 2
    @SamuelEdwinWard - yes it will allocate and populate a new array each time the code block is executed. If the list of allowed values is defined at compile time & essentially "static" for the lifetime of the application, you could define it as a const or static field which would reduce the GC overhead of the method if it is called frequently. – Trevor Pilley Jul 24 '13 at 13:43
9

You may use the switch statement:

switch (foo) {
    case "1":
    case "5":
    case "9":
        // ...
        break;
    case "2":
    case "4":
        // ...
        break;
}

If foo is a string, pay attention on case sensitivity.

joe
  • 8,344
  • 9
  • 54
  • 80
9

If you have multiple if conditions you should always consider using switch statements as compiler will create Jumptables whereever possible to increase speed. You should take a look here for speed test. Thing to note here is that if number of conditions is big enough to cover overheads, C# compiler will also create a HashTable object.

So this is a better approach,

switch (foo) {
case "1":
case "5":
case "9":
    // ...
    break;
case "2":
case "4":
    // ...
    break;
}
Ehsan
  • 31,833
  • 6
  • 56
  • 65