What effect does the `virtual` modifier have on an interface member?

66 views Asked by At

Say I have an interface like interface IThing { virtual string A => "A"; }. What effect does the virtual keyword have on implementing types here? Surprisingly, I couldn't find anything regarding this on either SO, dotnet GitHub pages and discussions, nor on learn.microsoft.com, possibly buried under all the unrelated content where the words interface and virtual appear in any combination. From my quick testing it doesn't appear to have any sensible effect. My intuition tells me to expect from the implementing classes' hierarchy to have been introduced the given virtual members, as if the base implementing class has declared them itself, but it's not the case.

For example:

class Thing: IThing
{   
}   
    
class Thing2: Thing
{
    override public string A => "!"; // ERROR: No suitable method found to override.
}

If I declare the A property on Thing, but do not declare it explicitly as virtual, it still won't compile. I also have the freedom apparently to just define them w/o the modifier despite it being present in the interface and it compiles. And code using IThing only sees the default implementation of A regardless of what I do unless the class implements the interface directly and not through inheritance in this setup.

Can somebody clarify the use of the virtual modifier on interface members? I use the latest stable language version of C#.

1

There are 1 answers

6
Guru Stron On BEST ANSWER

The following:

interface IThing { virtual string A => "A"; }

Is the so called default interface method and virtual as far as I can see actually does nothing because:

The virtual modifier may be used on a function member that would otherwise be implicitly virtual

I.e. it just an explicit declaration of the fact that interface method is virtual since the language team decided to not restrict such things.

See also the Virtual Modifier vs Sealed Modifier part of the spec:

Decisions: Made in the LDM 2017-04-05:

  • non-virtual should be explicitly expressed through sealed or private.
  • sealed is the keyword to make interface instance members with bodies non-virtual
  • We want to allow all modifiers in interfaces
  • ...

Note that default implemented IThing.A is not part of the Thing, hence you can't do new Thing().A, you need to cast to the interface first.

If you want to override the IThing.A in the Thing2 then you can implement the interface directly:

class Thing2 : Thing, IThing
{
     public string A => "!"; 
}

Console.WriteLine(((IThing)new Thing()).A); // Prints "A"
Console.WriteLine(((IThing)new Thing2()).A); // Prints "!"

Another way would be declaring public virtual string A in the Thing so your current code for Thing2 works:

class Thing : IThing
{
    public virtual string A => "A";
}   
    
class Thing2 : Thing
{
    public override string A => "!"; 
}

To understand meaning of sealed/virtual identifiers in interfaces you can create a second interface:

interface IThing {  string A => "A"; }
interface IThing2 : IThing {  string IThing.A => "B"; }
class Thing : IThing2 {}

Console.WriteLine(((IThing)new Thing()).A); // Prints "B"

While if you declare IThing.A as sealed then IThing2 will not compile:

interface IThing { sealed string A => "A"; }

interface IThing2 : IThing
{
    // Does not compile:
    // string IThing.A => "B";
}

class AntoherThing : IThing
{
    // Does not compile too:
    string IThing.A => "B";
}

Also check out the why virtual is allowed while implementing the interface methods? linked by wohlstad in the comments.