C# 11 introduced static abstract members in interfaces.
But from what I'm experimenting, it only force the direct child to implement these static abstract
members. That static abstract
modifier can't be used in an abstract class
.
An example of what I'm trying to do, which doesn't compile:
public interface IMyInterface
{
public static abstract void DoSomething();
}
public abstract class MyAbstractClass : IMyInterface
{
public static abstract void DoSomething(); //Error: Overridable method cannot be static
}
public class MyClass : MyAbstractClass
{
public static override void DoSomething() => Console.Log("Hello, World!");
}
But without an abstract class
between them, MyClass
does compile just fine:
public class MyClass : IMyInterface
{
public static void DoSomething() => Console.Log("Hello, World!");
}
Did I miss something? And why would it be the case?
I believe I already know the workaround of my case, but this way does not force every child of MyAbstractClass
to implement IMyInterface
, just like what we would do with regular interface members.
public interface IMyInterface
{
public static abstract void DoSomething();
}
public abstract class MyAbstractClass
{
}
public class MyClass : MyAbstractClass, IMyInterface
{
public static void DoSomething() => Console.Log("Hello, World!");
}
But from what I'm experimenting, it only force the direct child to implement these static abstract members. That
static abstract
modifier can't be used in anabstract class
.
Correct.
Did I miss something? And why would it be the case?
static abstract
members to a class
type.static
members are not invoked via a virtual-call (vtable lookup) because there's no this
reference from which to obtain a vtable reference from.static
(static
methods don't have the implicit
(and hidden) this
parameter).By example:
class SubclassA : IMyInterface
{
public static void DoSomething() => Console.WriteLine( "Explosive bolts, ten thousand volts, At a million miles an hour" );
}
class SubclassAB : SubclassA
{
public static void DoSomething() => Console.WriteLine( "Life is short and love is always over in the morning." );
}
class SubclassB : IMyInterface
{
public static void DoSomething() => Console.WriteLine( "Hot metal and methedrine." );
}
//
public void Main()
{
MyMethod( new SubclassA() );
MyMethod( new SubclassAB() );
MyMethod( new SubclassB() );
}
public void MyMethod( IMyInterface instance )
{
// At this point, how do you propose invoking `DoSomething`? (without using reflection)
// ...see the problem?
}
However, interface
types do support static abstract
members, but only because they're useful in the context of generic method constraints, as it allows generic code to specify methods for call-sites that are non-virtual: when the JIT/runtime instantiates a generic method it emits static method calls, not vtable-based calls.
(For Swift users, this is kinda like how protocol
types are not equivalent to C# or Java interface
types, because a protocol
supports non-boxing calls to value-types, whereas (in non-generic methods) C# interface
types are always treated as reference-types, but anyway).
I believe I already know the workaround of my case, but this way does not force every child of
MyAbstractClass
to implementIMyInterface
It sounds like you're using interface types as a kind-of linter, to guarantee that a set of class
types all follow some kind of common coding-convention and implement some common set of members (in this case, static
members), even if you never utilize that interface
anywhere in your program - but this is not what interface
types in C# are intended for.
Whereas if you make MyMethod
a generic method and use IMyInterface
as a constraint then it becomes useful because now you can invoke the static
method by using the type-parameter as a type-name:
public void MyGenericMethod<T>( T instance )
where T : IMyInterface
{
T.DoSomething();
}
public void Main()
{
GenericMethod<SubclassA>(); // Will print "Explosive bolts, ten thousand volts, At a million miles an hour"
GenericMethod<SubclassAB>(); // <-- This actually behaves the same as SubclassA.
GenericMethod<SubclassB>(); // Will print "Hot metal and methedrine."
}
...though the call with SubclassAB
behaves the same as SubclassA
, because with SubclassAB
the call to T.DoSomething()
gets routed to SubclassA.DoSomething()
even though SubclassAB.DoSomething()
exists.
...I don't know why this doesn't work (yet). The original proposal document simply puts "TBD" for this, and oddly I can't find any relevant bugs filed in the Roslyn repo, so I'll update this answer when I find out.
I suppose you can argue that there's no difference between a T : BaseClass
constraint and a T : IInterface
constraint - at least insofar as this code (below) arguably should be allowed to work, but it doesn't (CS0704)
public class BaseClass
{
public static void DoSomething() => Console.WriteLine( "Strange men rent strange flowers" );
}
public class DerivedClass : BaseClass
{
public static void DoSomething() => Console.WriteLine( "Mundane by day inane at night" );
}
public static void Main()
{
GenericMethod<AbstractClass>(); // Should print "Strange men rent strange flowers"
GenericMethod<DerivedClass >(); // Should print "Mundane by day inane at night"
}
public static void GenericMethod<T>()
where T : BaseClass
{
Type t = typeof(T);
T.DoSomething(); // CS0704
}