Article 102AM To base() or not to base(), that is the question

To base() or not to base(), that is the question

by
jonskeet
from Jon Skeet's coding blog on (#102AM)

Today I've been reviewing the ECMA-334 C# specification, and in particular the section about class instance constructors.

I was struck by this piece in a clause about default constructors:

If a class contains no instance constructor declarations, a default instance constructor is automatically provided. That default constructor simply invokes the parameterless constructor of the direct base class.

I believe this to be incorrect, and indeed it is, as shown here (in C# 6 code for brevity, despite this being the C# 5 spec that I'm reviewing; that's irrelevant in this case):

using System;class Base{ public int Foo { get; } public Base(int foo = 5) { Foo = foo; }}class Derived : Base{ }class Test{ static void Main() { var d = new Derived(); Console.WriteLine(d.Foo); // Prints 5 } }

Here the default constructor in Derived clearly doesn't execute a parameterless constructor in Base because there is no parameterless constructor in Base. Instead, it executes the parameterized constructor, providing the default argument value.

So, I considered whether we could reword the standard to something like:

If a class contains no instance constructor declarations, a default instance constructor is automatically provided. That default constructor simply invokes a constructor of the direct base class as if the default constructor contained a constructor initializer of base().

But is that always the case? It turns out it's not - at least not in Roslyn. There are more interesting optional parameters we can use than just int foo = 5. Let's have a look:

using System;using System.Runtime.CompilerServices;class Base{ public string Origin { get; } public Base([CallerMemberName] string name = "Unspecified", [CallerFilePath] string source = "Unspecified", [CallerLineNumber] int line = -1) { Origin = $"{name} - {source}:{line}"; }}class Derived1 : Base {}class Derived2 : Base{ public Derived2() {}}class Derived3 : Base{ public Derived3() : base() {}}class Test{ static void Main() { Console.WriteLine(new Derived1().Origin); Console.WriteLine(new Derived2().Origin); Console.WriteLine(new Derived3().Origin); } }

The result is:

Unspecified - Unspecified:-1Unspecified - Unspecified:-1.ctor - c:\Users\Jon\Test\Test.cs:23

When base() is explicitly specified, that source location is treated as the "caller" for caller member info attributes. When it's implicit (including when there's a default constructor), no source location is made available to the Base constructor.

This is somewhat compiler-specific - and I can imagine different results where the default constructor could specify a name but not source file or line number, and the declared constructor with an implicit call could specify the name and source file but no line number.

I would never suggest using this little tidbit of Roslyn implementation trivia, but it's fun nonetheless"


1536 b.gif?host=codeblog.jonskeet.uk&blog=717
External Content
Source RSS or Atom Feed
Feed Location http://codeblog.jonskeet.uk/feed/
Feed Title Jon Skeet's coding blog
Feed Link https://codeblog.jonskeet.uk/
Reply 0 comments