Understanding how static abstract/virtual interface methods interact with inheritance #7922
-
I have an existing .NET 6 library that uses both inheritance and interfaces, and am now attempting to add static abstract/virtual methods into those interfaces (as part of an upgrade to .NET 8). I'm attempting to understand how static abstract/virtual interface methods interact with class inheritance, but have stumbled on an issue that I can't explain, and haven't been able to find an explanation for (e.g., in the language feature proposal, in the LDM notes, etc). I apologize in advance if I've missed something obvious. 😊 Suppose we have an interface and two classes: public interface ICanDo<T> where T : ICanDo<T>
{
static virtual string Do() => throw new NotImplementedException();
}
public abstract class BaseThing<T> : ICanDo<T> where T : ICanDo<T> { }
public class DerivedThing : BaseThing<DerivedThing>
{
public static string Do() => "DerivedThing";
} My understanding would be that ICanDo<DerivedThing> d = new DerivedThing(); Similarly, if I use reflection and ask Now suppose I have this generic method: public static void DoAndPrint<T>() where T : ICanDo<T>
{
Console.WriteLine(T.Do());
} And, finally, suppose I make this call: DoAndPrint<DerivedThing>(); What I would expect to see printed is What I'm seeing instead is a Notably, this behavior changes if I change public class DerivedThing : BaseThing<DerivedThing>, ICanDo<DerivedThing> { ... } With that change, And that's the part I can't explain. Why should it matter that Many thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
This is what they are supposed to do. Here’s a blog post that should answer all your questions: https://andrewlock.net/understanding-default-interface-methods/ |
Beta Was this translation helpful? Give feedback.
-
Yes, but not in the way that you're thinking. Let's translate this to an instance-based version of the code: DoAndPrint<DerivedThing>();
static void DoAndPrint<T>() where T : ICanDo<T>, new()
{
Console.WriteLine(new T().Do());
}
public interface ICanDo<T> where T : ICanDo<T>
{
virtual string Do() => throw new NotImplementedException();
}
public abstract class BaseThing<T> : ICanDo<T> where T : ICanDo<T> { }
public class DerivedThing : BaseThing<DerivedThing>
{
public string Do() => "DerivedThing";
} Note here that we see the same behavior: a not-implemented exception is thrown. We can also see this if we move from a public interface ICanDo<T> where T : ICanDo<T>
{
static abstract string Do();
}
public abstract class BaseThing<T> : ICanDo<T> where T : ICanDo<T> { } // CS0535: 'BaseThing<T>' does not implement interface member 'ICanDo<T>.Do()'
public class DerivedThing : BaseThing<DerivedThing>
{
public static string Do() => "DerivedThing";
} And finally, for completeness, here's the instance-based abstract version: public interface ICanDo<T> where T : ICanDo<T>
{
string Do();
}
public abstract class BaseThing<T> : ICanDo<T> where T : ICanDo<T> { } // CS0535: 'BaseThing<T>' does not implement interface member 'ICanDo<T>.Do()'
public class DerivedThing : BaseThing<DerivedThing>
{
public string Do() => "DerivedThing";
} The key point here is that, like all interface members, the compiler doesn't search across the entire hierarchy to find the implementation. Each level needs to provide that implementation, and since you didn't provide an implementation in |
Beta Was this translation helpful? Give feedback.
Yes, but not in the way that you're thinking. Let's translate this to an instance-based version of the code:
Note here that we see the same behavior: a not-implemented exception is thrown. We can also see this if we move from a
static v…