Nirav
Nirav

Reputation: 187

Selecting from set of enum values

I have collection of items which are having one enum property list. Original property looks like

public class Content {
    List<State> States {get; set;}
}

where 'State' is enum with almost 15 options.

While iterating collection of Content objects, I want to check it States property has certain values like State.Important and State.Updated exists in States and set another string from it.

something like

if(item.States.Has(State.Important) && item.States.Has(State.Updated))
string toProcess = "Do";

How to do this using Linq or Lambda ?

Upvotes: 1

Views: 4049

Answers (7)

Matthew Watson
Matthew Watson

Reputation: 109567

This should work if you must use Linq:

if (item.States.Any(state => state == State.Important) && item.States.Any(state => state == State.Updated))

Otherwise just use Contains() like @ElRonnoco says.

(However if your states are flags (powers of 2), then this answer will be slightly different.)

The trouble with this approach is that it iterates over the collection fully twice if neither of the states are set. If this happens often, it will be slower than it could be.

You can solve it without linq in a single pass like so:

bool isUpdated = false;
bool isImportant = false;

foreach (var state in item.States)
{
    if (state == State.Important)
        isImportant = true;
    else if (state == State.Updated)
        isUpdated = true;

    if (isImportant && isUpdated)
        break;
}

if (isImportant && isUpdated)
{
    // ...
}

This is unlikely to be an issue unless you have very large lists which often don't have either of the target states set, so you're probably best off using El Ronnoco's solution anyway.

If you have a lot of states to deal with, you could simplify things by writing an extension method like so:

public static class EnumerableExt
{
    public static bool AllPredicatesTrueOverall<T>(this IEnumerable<T> self, params Predicate<T>[] predicates)
    {
        bool[] results = new bool[predicates.Length];

        foreach (var item in self)
        {
            for (int i = 0; i < predicates.Length; ++i)
                if (predicates[i](item))
                    results[i] = true;

            if (results.All(state => state))
                return true;
        }

        return false;
    }

I had some difficulty coming up for a name for this. It will return true if for each predicate there is at least one item in the sequence for which the predicate is true. But that's a bit long for a method name... ;)

Then your example would become:

if (item.States.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))

Here's some sample code that uses it:

enum State
{
    Unknown,
    Important,
    Updated,
    Deleted,
    Other
}

void run()
{
    IEnumerable<State> test1 = new[]
    {
        State.Important, 
        State.Updated, 
        State.Other, 
        State.Unknown
    };

    if (test1.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
        Console.WriteLine("test1 passes.");
    else
        Console.WriteLine("test1 fails.");

    IEnumerable<State> test2 = new[]
    {
        State.Important, 
        State.Other, 
        State.Other, 
        State.Unknown
    };

    if (test2.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
        Console.WriteLine("test2 passes.");
    else
        Console.WriteLine("test2 fails.");

    // And to show how you can use any number of predicates:

    bool result = test1.AllPredicatesTrueOverall
    (
        state => state == State.Important,
        state => state == State.Updated,
        state => state == State.Other,
        state => state == State.Deleted
    );
}

But perhaps the easiest is to write an extension method for IEnumerable<State> (if you only have the one state enum to worry about):

public static class EnumerableStateExt
{
    public static bool AllStatesSet(this IEnumerable<State> self, params State[] states)
    {
        bool[] results = new bool[states.Length];

        foreach (var item in self)
        {
            for (int i = 0; i < states.Length; ++i)
                if (item == states[i])
                    results[i] = true;

            if (results.All(state => state))
                return true;
        }

        return false;
    }
}

Then your original code will become:

if (item.States.AllStatesSet(State.Important, State.Updated))

and you can easily specify more states:

if (item.States.AllStatesSet(State.Important, State.Updated, State.Deleted))

Upvotes: 4

Kamran Shahid
Kamran Shahid

Reputation: 4124

Some sort of following implementation

Content obj = new Content();
obj.States = SomeMethod(); 

if(obj.States.Any(h => h == State.Important) && obj.States.Any(h => h == State.Updated))
{
   string toProcess = "Do";
}

Upvotes: 0

ken2k
ken2k

Reputation: 48975

Maybe you could consider using your enum as a set of flags, i.e. you can combine multiple states without having a list:

[Flags]
public enum State
{
    Important = 1,
    Updated = 2,
    Deleted = 4,
    XXX = 8
    ....
}

public class Content
{
    public State MyState { get; set; }
}

if ((myContent.MyState & State.Important) == State.Important
    && (myContent.MyState & State.Updated) == State.Updated)
{
    // Important AND updated
}

Upvotes: 0

Taj
Taj

Reputation: 1728

 var res = (from items in item
                       where items.States.Has(State.Important) && items.States.Has(State.Updated)
                       select new { NewProcess = "Do" }).ToList();


foreach (var result in res)
    {
        string result = result.NewProcess
    }

Try this

Upvotes: 0

C4stor
C4stor

Reputation: 8026

You could go with

!(new List<States>{State.Important, State.Updated}.Except(item.States).Any());

It's not really shorter, but easier if you have a huge number of states to check.

As long as you want to check that the item has all states needed, you just have to add new States to the first list.

Upvotes: 0

Paolo Tedesco
Paolo Tedesco

Reputation: 57192

List has a Contains method, so your code would be

if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
    string toProcess = "Do";

I see no real benefit in using Linq or a lambda expression here...

Upvotes: 1

El Ronnoco
El Ronnoco

Reputation: 11922

You don't need Linq. I don't thinq

if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
  string toProcess = "Do";

http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx

Upvotes: 2

Related Questions