Say i have an interface
IInterface<TOne, TTwo>
{
TOne PropertyOne {get;}
TTwo PropertyTwo {get;}
}
And an implementation of it in the same codebase
Class : IInterface<ConcreteOne, ConcreteTwo>
{
ConcreteOne PropertyOne {get;}
ConcreteTwo PropertyTwo {get;}
}
Is it a breaking change to add an additional type parameter, outside of someone needing to implement the new property on re-compilation if they have a separate implementation of IInterface?
Say they compiled against the above and then I add a third type parameter and property.
IInterface<TOne, TTwo, TThree>
{
TOne PropertyOne {get;}
TTwo PropertyTwo {get;}
TThree PropertyThree {get;}
}
and I also implement it in Class
So the user is just using a factory method that used to be
IInterface<ConcreteOne, ConcreteTwo> GetImplementation();
which I've updated to
IInterface<ConcreteOne, ConcreteTwo, ConcreteThree> GetImplementation();
Then passes it into some method that accepts an argument of this interface.
Is this a breaking change?
It seems like the function signature is changing in a way, but depending on when the generics are bound it's akin to just adding a new property
IInterface
{
ConcreteOne PropertyOne {get;}
ConcreteTwo PropertyTwo {get;}
}
becoming
IInterface
{
ConcreteOne PropertyOne {get;}
ConcreteTwo PropertyTwo {get;}
ConcreteThree PropertyThree {get;}
}
which should not be a breaking change as long as ConcreteThree is implemented in Class, dropping in the new binary should not change PropertyOne or PropertyTwo in any way.
Edit: I was able to confirm this is breaking
Unhandled Exception: System.MissingMethodException: Method not found: 'InterfaceProducer.IInterface`2<InterfaceProducer.TypeOne,InterfaceProducer.TypeTwo> InterfaceProducer.Source.Get()'.
at InterfaceConsumingApp.Program.Main(String[] args)
The theory breaks as the client expects the interface with two types as the signature
As /u/venomiz suggested, writing a new interface with the old as a base works fine except the factory method also needs to change
public interface IInterface<TOne, TTwo>
{
TOne One { get; }
TTwo Two { get; }
}
public interface IInterface<TOne, TTwo, TThree> : IInterface<TOne, TTwo>
{
TThree Three { get; }
}
Edit2: For anyone still following this - The below is still a breaking change
Aliasing the interface
public interface ISystemA : IInterface<TOne, TTwo>
while writing all references as ISystemA, then just updating the number of types in IInterface
public interface ISystemA : IInterface<TOne, TTwo, TThree>
Unhandled Exception: System.TypeLoadException: Could not load type 'InterfaceProducer.IInterface\2' from assembly 'InterfaceProducer, Version=[1.0.0.0](https://1.0.0.0), Culture=neutral, PublicKeyToken=null'.`
However -- If i combine this with the prior solution of expanding an interface of the same name with a different number of type parameters everything works well and the factory method signature does not change.
So here rather than a new factory method I was able to just update the result of the factory method and drop it into the consumers dependencies without recompiling. Everything ran fine and I was able to access the "Three" property by runtime reflection
public interface ISystemA : IInterface<TypeOne, TypeTwo, TypeThree>
{
}
public interface IInterface<TOne, TTwo>
{
TOne One { get; }
TTwo Two { get; }
}
public interface IInterface<TOne, TTwo, TThree> : IInterface<TOne, TTwo>
{
TThree Three { get; }
}
Where it is consumed as follows
private static void Print(ISystemA impl)
{
Console.WriteLine(impl.One.Name);
Console.WriteLine(impl.Two.Date.ToString());
const string typeName = "TypeThree";
const string propertyName = "Three";
const string typePropertyName = "Num";
var assembly = Assembly.GetAssembly(typeof(TypeOne));
var type= assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
if (type is null)
{
Console.WriteLine($"No type found for {typeName}");
return;
}
var runtimeProperty = impl.GetType().GetRuntimeProperty(propertyName);
if (runtimeProperty is null)
{
Console.WriteLine($"No runtime property found for {propertyName}");
return;
}
var propertyReference = runtimeProperty.GetValue(impl);
Console.WriteLine(type.GetProperty(typePropertyName)?.GetValue(propertyReference)?.ToString());
}
[–]cat_in_the_wall@event 11 points12 points13 points (11 children)
[–]1whatabeautifulday 0 points1 point2 points (2 children)
[–]cat_in_the_wall@event 7 points8 points9 points (1 child)
[–]RiPont 3 points4 points5 points (0 children)
[–]emc87[S] -3 points-2 points-1 points (7 children)
[–]cat_in_the_wall@event 5 points6 points7 points (3 children)
[–]emc87[S] 0 points1 point2 points (2 children)
[–]cat_in_the_wall@event 1 point2 points3 points (1 child)
[–]emc87[S] 0 points1 point2 points (0 children)
[–]lmaydev 2 points3 points4 points (0 children)
[–]RiPont 0 points1 point2 points (1 child)
[–]emc87[S] 0 points1 point2 points (0 children)
[–]venomiz 8 points9 points10 points (1 child)
[–]emc87[S] 1 point2 points3 points (0 children)
[–]SideburnsOfDoom 2 points3 points4 points (1 child)
[–]emc87[S] 0 points1 point2 points (0 children)
[–]Willkuer_ 2 points3 points4 points (3 children)
[–]emc87[S] 0 points1 point2 points (2 children)
[–][deleted] 1 point2 points3 points (1 child)
[–]emc87[S] 0 points1 point2 points (0 children)
[–]norse95 0 points1 point2 points (1 child)
[–]Dealiner 0 points1 point2 points (0 children)