Say we have a base interface defined in C# like so:
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 an API assembly against which custom applications compile and run. (The assembly also includes non-exposed classes which implement these interfaces and public factory methods for obtaining instances). All extant code uses ISub1
, there is no existing code which directly references IBase
. It was done this way in anticipation that we might eventually want to introduce a second derived interface ISub2
, as a peer of ISub1
, and that has now come to pass. Unfortunately though we find that ISub2
should not contain Prop2
(only Prop1 and some additional unique properties), hence we want to "demote" that property down into 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 of IBase
it seems like we should be able to do this with impunity (and I'm fairly sure we could do that in Java), but when attempting to do so we have run into a binary compatibility problem with code compiled against the old interface definitions. Specifically:
ISub1 s1 = ... // get an instance
s1.Prop2 = "help";
This code, when run against the new interface definition, fails with an exception as follows:
System.MissingMethodException : Method not found: 'Void MyNamespace.IBase.set_Prop2(System.String)'.
Note the reference to IBase
. I presume this to be because the seeming call to ISub1.set_Prop2
has been compiled with a tight binding to where Prop2
is actually introduced, in IBase
.
Can anyone help me with a way out of this conundrum? I.e. is there a way to re-factor the interfaces so that the definition of ISub2 is "clean" (does not include the extraneous Prop2)? Asking all existing applications to recompile is out of the question.
By writing it in TryRoslyn it becomes quite evident that there is a difference based on where you put the property in the interface:
Given:
and
(note that in both cases I'm using the
ISub1*
interface in C# code)The generated IL code is:
so the IL code "correctly" resolves to the interface where the property is really defined.