Is there way to simplify a LINQ statement using if-else statement in c#

360 Views Asked by At

I have a LINQ expression that works but I wanted to make it simpler and cleaner.

var tryCatchTerminator = true;

return tryCatchTerminator
            ? from varKeyword in MatchToken(SyntaxKind.VarKeyword)
            from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
            from terminator in MatchToken(SyntaxKind.SemiColon).OptionalOrDefault()
            select (StatementSyntax) new VarDeclarationStatement(varKeyword, declarations, terminator)
            : from varKeyword in MatchToken(SyntaxKind.VarKeyword)
            from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
            select (StatementSyntax) new VarDeclarationStatement(varKeyword, declarations, Token<SyntaxKind>.Empty);

I looked all over the internet for some way to include an if statement inside the LINQ expression where I could stop if some condition is met and return an object... or continue to execute another query if the condition is not met.

Maybe this is obvious but I'm really clueless.

4

There are 4 best solutions below

6
Enigmativity On BEST ANSWER

It seems to me that this should work for you:

return
    from varKeyword in MatchToken(SyntaxKind.VarKeyword)
    from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
    from terminator in tryCatchTerminator ? MatchToken(SyntaxKind.SemiColon).OptionalOrDefault() : new[] { Token<SyntaxKind>.Empty } 
    select (StatementSyntax)new VarDeclarationStatement(varKeyword, declarations, terminator);

The key to it working is just giving the from terminator expression a single element array to return the empty token if tryCatchTerminator is false.

0
timur On

It's hard to tell if this will work based on your code sample, but I don't see why you couldn't check for the condition inside the LINQ query:

return from varKeyword in MatchToken(SyntaxKind.VarKeyword)
          from declarations in ParseVarDeclarationClause.AtLeastOnceDelimitedBy(MatchToken(SyntaxKind.Comma))
          from terminator in MatchToken(SyntaxKind.SemiColon).DefaultIfEmpty()
          select (StatementSyntax)new VarDeclarationStatement(varKeyword, declarations, tryCatchTerminator ? terminator : Token<SyntaxKind>.Empty); // check here and pass correct value to VarDeclarationStatement
0
PSGuy On

If I understand your question properly, then no, there's no (built-in) way to "stop" a query once it is started. If you wanted to add what amounts to a cancellation predicate during enumeration, which signals whether the enumeration should continue, the easiest way to do this would be by creating a custom iterator. Such an implementation might look like this:

public sealed class BreakingEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _query;
    private readonly Predicate<T> _continuePredicate;

    public BreakingEnumerable(IEnumerable<T> query, Predicate<T> predicate)
    {
        _query = query ?? throw new ArgumentNullException(nameof(query));
        _continuePredicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var item in _query)
        {
            if (_continuePredicate(item))
            {
                yield return item;
            }
            else
            {
                yield break;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Of course, you'd want to make this part of your query, so you'd probably want an extension methods class to convert a query to this custom enumerable:

public static class BreakingEnumerableExtensions {
    public static BreakingEnumerable<T> WithTerminationClause<T>(
        this IEnumerable<T> query,
        Predicate<T> breakCondition)
    {
        return new BreakingEnumerable<T>(query, breakCondition);
    }
}

And here's the actual usage:

static void Main(string[] args)
{
    var enumerable = Enumerable.Range(1, 100);

    var array = enumerable.WithTerminationClause(i => i > 100).ToArray();
    Console.WriteLine($"Enumerable with termination clause array length: {array.Length}");

    array = enumerable.Where(i => i < 20).WithTerminationClause(i => i % 2 == 0)
        .ToArray();

    Console.WriteLine($"Enumerable with termination clause length: {array.Length}");
}

Which produces the result:

Enumerable with termination clause array length: 0
Enumerable with termination clause length: 9

This can be chained to produce some minor optimizations:

// Outputs: `Query results: [100, 200, 300]`
var enumerable = Enumerable.Range(1, 100);

var sub = enumerable.WithTerminationClause(i => i <= 3)
    .Select(i => i * 100);
Console.WriteLine("Query results: [{0}]", string.Join(", ", sub));

The only "hitch" is you would never want to use this unless you could guarantee some form of ordering: all numbers appear in an ordered sequence, for example. If you didn't enforce this guarantee, then you could possibly produce incorrect results from your program.

Hope this helps!

0
xDGameStudios On

I ended up creating a passthrough parser.. that doesn't consume tokens and returns an empty token.

    private static TokenListParser<SyntaxKind, StatementSyntax> ParseExpressionStatement(
            bool lookForTerminator)
    {
        return from expression in ParsePrefixExpression.Or(ParseCallExpression())
                from terminator in lookForTerminator
                    ? MatchToken(SyntaxKind.SemiColon).OptionalOrDefault()
                    : PassThrough<SynaxKind>()
                select (StatementSyntax) new ExpressionStatementSyntax(expression, terminator);
    }

    private static TokenListParser<T, Token<T>> PassThrough<T>(Token<T> empty)
    {
        return input =>
        {
            var output = input.ConsumeToken();
            return TokenListParserResult.Value(Token<T>.Empty, output.Location, output.Location);
        };
    }