Reputation: 57
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.
Upvotes: 4
Views: 1751
Reputation: 1324
While this question is quite old, I want to mention a similar problem I had, when splitting up an interface into a base and inheriting type. Because it was part of a Nuget package with the same major release version, it had to be downward compatible with previous versions. I solved it by duplicating the members in the original interface with the "new" keyword.
MissingMethodException after extracting base interface
Upvotes: 0
Reputation: 23709
First, you should hide ISub2.Prop2
by implementing it explicitly. Then, depending on why ISub2
should not contain Prop2
, you should either deprecate that implementation using the ObsoleteAttribute attribute or throw an InvalidOperationException from both accessors.
Upvotes: 0
Reputation: 239744
Basically, this:
interface ISub1: IBase
Just says "any class that implements ISub1
will promise to also implement IBase
". There's no mixing of the methods defined within each interface, such that it also means "ISub1
contains 3 properties, Prop1
- Prop3
".
So that's why it's not working. ISub1
is currently defined to require exactly one property called Prop3
.
Upvotes: 1
Reputation: 45096
Kind of hacky and not sure it will work but maybe 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; }
}
Upvotes: 2
Reputation: 111890
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:
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'm using the ISub1*
interface in C# code)
The generated IL code is:
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)
so the IL code "correctly" resolves to the interface where the property is really defined.
Upvotes: 1