S3358 — Ternary operators should not be nested

Language
C#
Type
Code smell
Severity
Major
Tags
confusing

Why is this an issue?

Nested ternary operators are hard to read because the mapping between each condition and its result is not immediately obvious, and the order in which the conditions are evaluated is easy to misjudge. Each extra level of nesting increases the effort required to understand which branch produces which value.


public string GetAttendanceStatus(Student student)
{
    return student.IsPresent ? "Present" : student.HasExcuse ? "Excused" : "Absent";  // Noncompliant
}

Exceptions

The rule does not flag nested ternary operations inside a lambda expression that is converted to an expression tree (Expression<TDelegate>), such as the lambdas passed to IQueryable<T> methods (Select, Where, OrderBy, and similar) when using Entity Framework Core, LINQ to SQL, or another query provider.

None of the usual fixes are available there: a lambda converted to an expression tree cannot have a statement body, so the ternary operation cannot be rewritten into an if statement (compiler error CS0834). A switch expression is not an option either, because expression trees cannot contain one at all (compiler error CS8514). Extracting the logic into a separate method compiles, but most query providers cannot translate a call to a user-defined method, so the query fails at runtime instead. The nested ternary operation is therefore usually the only construct that both compiles and translates reliably, typically into a SQL CASE WHEN.


IQueryable<string> query = context.Students
    .Select(s => s.IsGraduated ? "Graduated" : s.IsEnrolled ? "Enrolled" : "Withdrawn"); // Compliant: expression tree, translated to a SQL CASE WHEN

A switch expression or an extracted method can still be used here, but only after the query has been materialized, for example after AsEnumerable() or ToList().

How to fix it

Extract the nested ternary operation into a separate statement or expression, so that each condition and its result can be read on their own. Depending on the situation, this can be achieved by:

Code examples

Noncompliant code example


public string GetAttendanceStatus(Student student)
{
    return student.IsPresent ? "Present" : student.HasExcuse ? "Excused" : "Absent";  // Noncompliant
}

Compliant solution


public string GetAttendanceStatus(Student student)
{
    if (student.IsPresent)
    {
        return "Present";
    }
    return student.HasExcuse ? "Excused" : "Absent";
}

A switch expression is a good substitute when all conditions test the same expression:

Noncompliant code example


public string GetGradeLabel(Student student) =>
    student.Score >= 90 ? "Excellent" : student.Score >= 70 ? "Good" : "NeedsImprovement"; // Noncompliant

Compliant solution


public string GetGradeLabel(Student student) =>
    student.Score switch
    {
        >= 90 => "Excellent",
        >= 70 => "Good",
        _ => "Needs improvement",
    };

When the nested ternary is just one part of a larger statement, extracting it into a well-named method keeps the surrounding code readable:

Noncompliant code example


public void PrintEnrollmentStatus(Student student)
{
    Console.WriteLine($"Status: {(student.IsGraduated ? "Graduated" : student.IsEnrolled ? "Enrolled" : "Withdrawn")}"); // Noncompliant
}

Compliant solution


public void PrintEnrollmentStatus(Student student)
{
    Console.WriteLine($"Status: {GetEnrollmentStatus(student)}");
}

private static string GetEnrollmentStatus(Student student)
{
    if (student.IsGraduated)
    {
        return "Graduated";
    }
    return student.IsEnrolled ? "Enrolled" : "Withdrawn";
}

Resources

Documentation

↑ Back to top