Tuesday, August 18, 2009

Name resolution in optional parameter initializers

How do you think, what is the meaning of each T in the following C# 4.0 program? Will this code compile?


class A
{
public class T
{
public const int X = 1;
}

public void Foo<T>(int x = T.X, T y = default(T))
{
}
}

I will give the explanation in the next post.

Monday, June 9, 2008

An Implicit Conversion Can Exist, But Still Fail At Compile-Time

One important thing to know about implicit conversions in C#: they can exist but still fail at compile-time. Yes, I mean a very compile-time error, not a runtime exception. It can lead to subtle surprises in overload resolution. Let us look at examples:

1)

class A
{
static void Main()
{
B b = new B();
Foo(b);
}

static void Foo(A x) { }
static void Foo(IB x) { }

public static implicit operator A(B x)
{
return null;
}
}

interface IB {}

class B : IB
{
public static implicit operator A(B x)
{
return null;
}
}

The compilation of this program leads to the following compile-time error:

error CS0121: The call is ambiguous between the following
methods or properties: 'A.Foo(A)' and 'A.Foo(IB)'

The second overload Foo(IB) is obviously applicable, because there is an implicit conversion from the class B to the interface IB, which it implements. Indeed, if you comment out the first overload Foo(A), the compilation succeeds. But what will happen, if you comment out the second overload and leave Foo(A)? You will get an ambiguity in the conversion from B to A, because the compiler will find two user-defined conversion operators from B to A. Interesting part begins here! Does an implicit conversion from B to A exist? Is the method Foo(A) applicable?

The right answer is 'Yes'. The implicit conversion exist, but it is ambiguous and fails at compile-time. Nevertheless, the method Foo(A) is applicable. So, if you have both overloads, the invocation of Foo becomes ambiguous.

Note: If you have ReSharper installed, you might notice that its error analysis shows no errors in this code. So, it does not know about this subtlety. I has already logged a bug for this.

2)

using System;

class A
{
static void Main()
{
Bar(Foo);
}

static void Foo(object x) { }

static void Bar(Action<object> x){}
static void Bar(Action<int> x){}
}

The compilation of this program leads to the following compile-time error:

error CS0121: The call is ambiguous between the following
methods or properties: 'A.Bar(System.Action<object>)'
and 'A.Bar(System.Action<int>)'

Again, the first overload Bar(Action<object>) is obviously applicable. If you comment out it and leave only Bar(Action<int>), you will get the following error:

error CS0123: No overload for 'A.Foo(object)'
matches delegate 'System.Action<int>'

This is correct, because a contravariance in parameter types works only for reference types. So, does an implicit conversion from the method group Foo to Action<int> exist? Again, we are tempted to answer 'No', but the correct answer is 'Yes. Exists, but fails an compile-time'. So, the overload Bar(Action<int> x) is applicable, and we have an ambiguity.

Note: Again, ReSharper is wrong here.

There are many reasons, why an existing implicit conversion from a method group to a delegate type may fail at compile-time:

  • Overload resolution cannot pick the best method from the method group being converted.
  • The best method is generic, and constraints on its type parameters are not satisfied.
  • The best method has a wrong return type.
  • For at least one parameter, the conversion from the type of the parameter of the delegate to the type of the parameter of the best method is not an identity or an implicit reference conversion.

3)

using System;
using System.Linq.Expressions;

class A
{
static void Main()
{
Bar(x => { return x; });
}

static void Bar(Func<int,int> x) { }
static void Bar(Expression<Func<int, int>> x) { }
}

A lambda-expression with a statement body cannot be converted to an expression tree type, but the conversion still exists, since there is a conversion to the corresponding delegate type. The invocation of Bar is ambiguous.

Note: ReSharper is correct here.

Tuesday, March 11, 2008

The Surprising Unboxing

The unboxing to a non-nullable value type seems to be a simple thing, doesn't it? Check whether the object instance is a boxed value of the given value type. If yes, copy the value out of the instance. If no, throw System.NullReferenceException if the object instance is null, or throw System.InvalidCastException otherwise. That is what The C# Specification says. Simply? Not exactly.


Yesterday I noticed that the actual behavior differs from the specified. Compile and run the following code:

using System;

enum E { }

class Program
{
static void Main()
{
// box a value of type E
object x = new E();

// unbox the result to System.Int32
int y = (int) x;

// the unboxing succeeded, 0 is printed out
Console.WriteLine(y);
}
}

We boxed the value of type E, and successfully unboxed it to a different type, System.Int32. What about arrays of these types? Are they covariant (God forbid)?

using System;

enum E { }

class Program
{
static void Main()
{
object x = new E[] { new E() };
int[] y = (int[]) x;

// the conversion succeeded, 0 is printed out
Console.WriteLine(y[0]);
}
}

From The C# Specification's point of view, it is a nonsense. Only arrays of reference type can be covariant. "Ah," one can say, "the underlying type of E is int. The trick should be here!" No.

using System;

enum E { }

class Program
{
static void Main()
{
object x = new E[] { new E() };
IntPtr[] y = (IntPtr[]) x;

// the conversion succeeded, 0 is printed out
Console.WriteLine(y[0]);
}
}

The type System.IntPtr has nothing to do with E (only their sizes match), but the conversion still succeeds.

Sunday, February 3, 2008

Implicitly Typed Local Variables And A Stackalloc Initializer

The C# 3.0 contains a great new feature: implicitly typed local variables. This feature is described in The C# 3.0 Specification, 8.5.1 Local variable declarations. That paragraph includes the following rule:

Implicitly typed local variable declarations are subject to the following restrictions:
.......

  • The local-variable-initializer must be an expression.

What does it mean? Generally, a local-variable-initializer can be of the following forms: an expression, an array-initializer and a stackalloc-initializer. An array-initializer can be used to initialize a variable of an array type in a concise form. A stackalloc-initializer can be used in an unsafe context to perform a stack allocation and initialize a variable of a pointer type with a pointer to the newly allocated memory block. Neither an array-initializer nor a stackalloc-initializer is an expression (if you try to use them as expressions, you will get a bunch of errors during the syntactic analysis). So, it looks like it is not possible to use an array-initializer or a stackalloc-initializer as an initializer of an implicitly typed local variable. Let us check it:

class Program
{
// Do not forget to use the /unsafe option
// when compiling this code
unsafe static void Main()
{
// error CS0820: Cannot initialize an implicitly-typed
// local variable with an array initializer
var x = { 1, 2, 3 };

// No error here!
var p = stackalloc int[1];
}
}

It turns out, that the compiler accepts a stackalloc-initializer as an initializer of an implicitly typed local variable! I hope, The Specification will be fixed soon to reflect this.

Sometimes The C# Compiler Does Not Validate Supplied Type Arguments

Recently I noticed that sometimes the C# compiler does not validate type arguments supplied in a constructed type, if the constructed type itself occurs within a constraint of a type parameter. Consider the following code:

class A
{
public interface I { }
}

class C<T> : A where T : C<int>.I
{
}

Obviously, the type argument int does not satisfy the corresponding constraint, but the compiler does not issue an error here. In fact, you are allowed to supply an absolute garbage in this context:

class A
{
public interface I { }
}

class C<T> : A where T : C<object*>.I
{
}

Note that generally it is not legal to declare a pointer to a managed type (object), to use pointers in a safe context or to supply a pointer as a type argument. But in this case, the compiler does not enforce these rules. Why?

The point is that the C# language allows to refer to a nested type, declared in a non-generic class, both through its declaring class and through a class, derived from the declaring class (but it is not possible to capture this difference in the IL or metadata). If the derived class is a constructed generic class, then type arguments supplied for that class are actually discarded. It leads to an interesting conclusion that even if a type-name involves type arguments, it still may denote a non-generic type.

So, in the previous examples the constraint is actually interpreted in the following way:

class A
{
public interface I { }
}

class C<T> : A where T : A.I
{
}

Thus, no invalid types get into the compiled assembly, and it is great! But the behavior of the compiler still looks inconsistent. Note that if the same fake generic type (C<int>.I) occurs within the body of a method, a compile-time error is issued:

class A
{
public interface I { }
}

class C<T> : A where T : C<int>.I
{
static void Foo()
{
// error CS0315: The type 'int' cannot be used as
// type parameter 'T' in the generic type or method 'C<T>'.
// There is no boxing conversion from 'int' to 'A.I'.
C<int>.I x;
}
}