.Net Binary Compatibility
Let's say we have a base interface defined in C # like:
interface IBase
{
int Prop1 { get; set }
string Prop2 { get; set }
}
Then we have a derived interface as follows:
interface ISub1: IBase
{
int Prop3 { get; set }
}
These interfaces are defined in the API assembly from which custom applications are compiled and run. (The assembly also includes unaffected classes that implement these interfaces and the public factory methods for getting instances.) All existing code uses ISub1
, there is no existing code that directly links to IBase
. This was done in anticipation that we would eventually want to introduce the second derived interface ISub2
as a peer ISub1
, and that is now. Unfortunately, although we found that ISub2
we shouldn't contain Prop2
(just Prop1 and some additional unique properties), so we want to "downgrade" this property to ISub1
, resulting in the following revised interfaces:
interface IBase
{
int Prop1 { get; set }
}
interface ISub1: IBase
{
string Prop2 { get; set }
int Prop3 { get; set }
}
interface ISub2: IBase
{
string Prop4 { get; set }
}
Given that there are no consumers IBase
, it looks like we should be able to do this with impunity (and I'm sure we could do it in Java), but when trying to do this we ran into a binary compatibility issue with code compiled against the old interface definitions ... In particular:
ISub1 s1 = ... // get an instance
s1.Prop2 = "help";
This code, when run with a new interface definition, fails with an exception like this:
System.MissingMethodException: Method not found: 'Void MyNamespace.IBase.set_Prop2 (System.String).
Pay attention to the link to IBase
. I guess this is because the seeming call ISub1.set_Prop2
was hardcoded to where it was Prop2
actually put in IBase
.
Can anyone help me out of this puzzle? That is, is there a way to rephrase the interfaces so that the definition of ISub2 is "clean" (doesn't include the extraneous Prop2)? Asking to recompile all existing applications is out of the question.
source to share
By writing it to TryRoslyn , it becomes abundantly clear that there is a difference based on where you put the property in the interface:
Given:
interface ISub1A: IBaseA
{
int Prop3 { get; set; }
}
interface IBaseA
{
int Prop1 { get; set; }
string Prop2 { get; set; }
}
interface ISub1B: IBaseB
{
int Prop3 { get; set; }
string Prop2 { get; set; }
}
interface IBaseB
{
int Prop1 { get; set; }
}
and
ISub1A a = null;
a.Prop2 = "Hello";
ISub1B b = null;
b.Prop2 = "Hello";
(note that in both cases I am using the interface ISub1*
in C # code)
Generated IL Code:
IL_0001: ldstr "Hello"
IL_0006: callvirt instance void IBaseA::set_Prop2(string)
IL_000b: ldnull
IL_000c: ldstr "Hello"
IL_0011: callvirt instance void ISub1B::set_Prop2(string)
therefore the IL code "correctly" resolves the interface where the property is actually defined.
source to share
Kind of hacks and not sure if it will work, but it might be worth a try
interface IBase0
{
int Prop1 { get; set; }
}
interface IBase : IBase0
{
int Prop1 { get; set; }
string Prop2 { get; set; }
}
interface ISub1: IBase
{
int Prop3 { get; set; }
}
interface ISub2 : IBase0
{
int Prop4 { get; set; }
}
source to share
Mainly:
interface ISub1: IBase
Simply says "any class that implements ISub1
promises to also implement IBase
". There is no mixing of methods defined in each interface, so this also means that " ISub1
contains 3 properties, Prop1
- Prop3
".
So why doesn't it work. ISub1
is currently defined to require exactly one property called Prop3
.
source to share
First, you have to hide to ISub2.Prop2
implement it explicitly . Then, depending on why ISub2
you shouldn't contain Prop2
, you must either drop that implementation using the ObsoleteAttribute , or throw an InvalidOperationException on both accessories.
source to share