Share via


Deviations from the C# standard

This document lists inconsistencies between Roslyn and the C# standard where they are known, organized by standard section.

Conversions

Implicit enum conversions from zero

From §10.2.4:

An implicit enumeration conversion permits a constant_expression (§11.21) with any integer type and the value zero to be converted to any enum_type and to any nullable_value_type whose underlying type is an enum_type.

Roslyn performs implicit enumeration conversions from constant expressions with types of float, double and decimal as well:

enum SampleEnum
{
    Zero = 0,
    One = 1
}

class EnumConversionTest
{
    const float ConstFloat = 0f;
    const double ConstDouble = 0d;
    const decimal ConstDecimal = 0m;

    static void PermittedConversions()
    {
        SampleEnum floatToEnum = ConstFloat;
        SampleEnum doubleToEnum = ConstDouble;
        SampleEnum decimalToEnum = ConstDecimal;
    }
}

Conversions are (correctly) not permitted from constant expressions which have a type of bool, other enumerations, or reference types.

Member lookup

From §12.5.1:

  • Finally, having removed hidden members, the result of the lookup is determined:
    • If the set consists of a single member that is not a method, then this member is the result of the lookup.
    • Otherwise, if the set contains only methods, then this group of methods is the result of the lookup.
    • Otherwise, the lookup is ambiguous, and a binding-time error occurs.

Roslyn instead implements a preference for methods over non-method symbols:

var x = I.M; // binds to I1.M (method)
x();

System.Action y = I.M; // binds to I1.M (method)

interface I1 { static void M() { } }
interface I2 { static int M => 0;   }
interface I3 { static int M = 0;   }
interface I : I1, I2, I3 { }
I i = null;
var x = i.M; // binds to I1.M (method)
x();

System.Action y = i.M; // binds to I1.M (method)

interface I1 { void M() { } }
interface I2 { int M => 0;   }
interface I : I1, I2 { }

Assumptions about well-known types/members

The compiler is free to make assumptions about the shape and behavior of well-known types/members. It may not check for unexpected constraints, Obsolete attribute, or UnmanagedCallersOnly attribute. It may perform some optimizations based on expectations that the types/members are well-behaved. Note: the compiler should remain resilient to missing well-known types/members.

Interface partial methods

Interface partial methods are implicitly non-virtual, unlike non-partial interface methods and other interface partial member kinds, see a related breaking change and LDM 2025-04-07.