Reputation: 11597
I read in CLR via C# 4th edition on chapter 6:
If you define a method as nonvirtual, you should never change the method to virtual in the future. The reason is because some compilers will call the nonvirtual method by using the call instruction instead of the callvirt instruction. If the method changes from nonvirtual to virtual and the referencing code is not recompiled, the virtual method will be called nonvirtually, causing the application to produce unpredictable behavior. If the referencing code is written in C#, this is not a problem, because C# calls all instance methods by using callvirt. But this could be a problem if the referencing code was written using a different programming language.
but I can't quite figure out what kind of unpredictable behavior may occur? Can you get an example or explain what kind of unexpected behavior is the author is referring to?
Upvotes: 0
Views: 154
Reputation: 40838
The docs for the call OpCode indicate that it acceptable to call a virtual method non-virtually. It will just invoke the method based on the compiled type in the IL rather runtime type information.
However, from what I can tell the method will fail verification if you call a virtual method non-virtually. Here is a short test program where we will dynamically emit the IL for invoking a method (either virtually or non-virtually), compile, and run it:
using System.Reflection;
using System.Reflection.Emit;
public class Program
{
public static void Main()
{
// Base parameter, Base method info
CreateAndInvokeMethod(false, new Base(), typeof(Base), typeof(Base).GetMethod("Test"));
CreateAndInvokeMethod(true, new Base(), typeof(Base), typeof(Base).GetMethod("Test"));
CreateAndInvokeMethod(false, new C(), typeof(Base), typeof(Base).GetMethod("Test"));
CreateAndInvokeMethod(true, new C(), typeof(Base), typeof(Base).GetMethod("Test"));
Console.WriteLine();
// Base parameter, C method info
CreateAndInvokeMethod(false, new Base(), typeof(Base), typeof(C).GetMethod("Test"));
CreateAndInvokeMethod(true, new Base(), typeof(Base), typeof(C).GetMethod("Test"));
CreateAndInvokeMethod(false, new C(), typeof(Base), typeof(C).GetMethod("Test"));
CreateAndInvokeMethod(true, new C(), typeof(Base), typeof(C).GetMethod("Test"));
Console.WriteLine();
// C parameter, C method info
CreateAndInvokeMethod(false, new C(), typeof(C), typeof(C).GetMethod("Test"));
CreateAndInvokeMethod(true, new C(), typeof(C), typeof(C).GetMethod("Test"));
}
private static void CreateAndInvokeMethod(bool useVirtual, Base instance, Type parameterType, MethodInfo methodInfo)
{
var dynMethod = new DynamicMethod("test", typeof (string),
new Type[] { parameterType });
var gen = dynMethod.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
OpCode code = useVirtual ? OpCodes.Callvirt : OpCodes.Call;
gen.Emit(code, methodInfo);
gen.Emit(OpCodes.Ret);
string res;
try
{
res = (string)dynMethod.Invoke(null, new object[] { instance });
}
catch (TargetInvocationException ex)
{
var e = ex.InnerException;
res = string.Format("{0}: {1}", e.GetType(), e.Message);
}
Console.WriteLine("UseVirtual: {0}, Result: {1}", useVirtual, res);
}
}
public class Base
{
public virtual string Test()
{
return "Base";
}
}
public class C : Base
{
public override string Test()
{
return "C";
}
}
The output:
UseVirtual: False, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: True, Result: Base
UseVirtual: False, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: True, Result: CUseVirtual: False, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: True, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: False, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: True, Result: System.Security.VerificationException: Operation could destabilize the runtime.UseVirtual: False, Result: System.Security.VerificationException: Operation could destabilize the runtime.
UseVirtual: True, Result: C
Upvotes: 2
Reputation: 137425
Suppose that you make a library with the following classes:
// Version 1
public class Fruit {
public void Eat() {
// eats fruit.
}
// ...
}
public class Watermelon : Fruit { /* ... */ }
public class Strawberry : Fruit { /* ... */ }
Supposed the end-user of the library writes a method that takes a Fruit
and calls its Eat()
method. Its compiler see a non-virtual function call and emits a call
instruction.
Now later you decide that eating a strawberry and eating a watermelon are, um, rather different, so you do something like:
//Version 2
public class Fruit {
public virtual void Eat() {
// this isn't supposed to be called
throw NotImplementedException();
}
}
public class Watermelon : Fruit {
public override void Eat() {
// cuts it into pieces and then eat it
}
// ...
}
public class Strawberry : Fruit {
public override void Eat() {
// wash it and eat it.
}
// ...
}
Now your end-user's code suddenly crashes with a NotImplementedException
, because non-virtual calls on a base class reference always go to the base class method, and everyone is bewildered because your end-user only used Watermelon
and Strawberry
for its Fruit
s and both have fully implemented Eat()
methods...
Upvotes: 1
Reputation: 700670
If a virtual method would be called as a nonvirtual method, that would change which method that would actually be called.
When you call a virtual method, it's the actual type of the object that determines which method is called, but when you call a nonvirtual method it's the type of the reference that determines which method is called.
Lets say that we have a base class and a subclass:
public class BaseClass {
public virtual void VMedthod() {
Console.WriteLine("base");
}
}
public class SubClass : BaseClass {
public override void VMethod() {
Console.WriteLine("sub");
}
}
If you have a reference of the type of the base class, assigns it an instance of the subclass, and call the method, it's the overriding method that will be called:
BaseClass x = new SubClass();
x.VMethod(); // shows "sub"
If the virtual method would be called as a nonvirtual method instead, it would call the method in the base class insetad and show "base".
This is a simplified example of course, you would have the base class in one library and the subclass in another for the problem to possibly occur.
Upvotes: 1