Reputation: 2074
I'm looking for the best way to implement the following situation (.NET 3.5):
interface IGetThing<T>
{
T Get();
}
class BaseGetter<A> : IGetThing<A> where A : new()
{
public virtual A Get()
{
return new A();
}
}
class DerivedGetter<B, A> : Base, IGetThing<B> where B : A, new() where A : new()
{
public override A Get()
{
return Get(); //B version
}
public new virtual B Get()
{
return new B();
}
}
I've evaluated posts like This one, but I cannot see a solution that it would provide that is equivalent.
I've seen suggestions that I use explicit interface implementation to do something similar, but I don't see how that solves the inheritance issue:
If Get() was implemented explicitly in both places, it wouldn't solve the problem of: ((IGetThing<A>)new DerivedGetter<B, A>()).Get()
calling the base method, instead of the desired derived method.
Attempting to implement both IGetThing and IGetThing in DerivedGetter causes a compilation exception. ('DerivedGetter' cannot implement both 'IGetThing' and 'IGetThing' because they may unify for some type parameter substitutions)
Also, attempting to re-implement BaseGetter's explicit implementation (IGetThing<A>.Get()
) in DerivedGetter also provides a compilation exception (The obvious 'DerivedGetter.IGetThing<...>.Get()': containing type does not implement interface 'IGetThing')
The goal is to hide and override the base's Get() when using Derived.
Does anyone have any ideas?
EDIT: The overall solution would preferably be able to scale to multiple layers of derived classes.
As an aside, this only started giving me compilation issues when I changed from .NET 4 to .NET 3.5.
Upvotes: 4
Views: 309
Reputation: 2074
After hours of thinking, and a good night's sleep, I've come up with a viable solution that retains the original interface, and scales to multiple levels of inheritance without exploding too much.
interface IGetThing<T>
{
T Get();
}
class BaseGetter<A> : IGetThing<A>
where A : new()
{
public A Get()
{
A result;
GetInternal(out result);
return result;
}
protected virtual void GetInternal(out A target)
{
target = new A();
}
}
class DerivedGetter<B, A> : BaseGetter<A>, IGetThing<B>
where B : A, new()
where A : new()
{
public new B Get()
{
B result;
GetInternal(out result);
return result;
}
protected override void GetInternal(out A target)
{
target = Get();
}
protected virtual void GetInternal(out B target)
{
target = new B();
}
}
class Derived2Getter<C, B, A> : DerivedGetter<B, A>, IGetThing<C>
where C : B, new()
where B : A, new()
where A : new()
{
public new C Get()
{
C result;
GetInternal(out result);
return result;
}
protected override void GetInternal(out B target)
{
target = Get();
}
protected virtual void GetInternal(out C target)
{
target = new C();
}
}
When implemented an run through:
class Aa { }
class Bb : Aa { }
class Cc : Bb { }
class Program
{
static void Main(string[] args)
{
BaseGetter<Aa> getter = new DerivedGetter<Bb, Aa>();
Console.WriteLine("Type: " + getter.Get().GetType().Name);
getter = new Derived2Getter<Cc, Bb, Aa>();
Console.WriteLine("Type: " + getter.Get().GetType().Name);
}
}
The console output is
Type: Bb
Type: Cc
Upvotes: 0
Reputation: 15415
This new implementation takes your comments into account. I don't mind saying this - this is weird.
First thing - you have to do away with static generic constraints that the derived getter's generic parameters are related. You can still check this, but it's a run time.
interface IGetThing<T>
{
T Get();
}
class BaseGetter<A> : IGetThing<A> where A : new()
{
public BaseGetter()
{
var generics = this.GetType().GetGenericArguments();
for (var i = 0; i < generics.Length - 1; i++)
{
if (generics[i].BaseType != generics[i+1])
{
throw new ArgumentException(
string.Format("{0} doesn't inherit from {1}",
generics[i].FullName,
generics[i + 1].FullName));
}
}
getters = new Dictionary<Type, Func<object>>();
getters.Add(typeof(A), () => new A());
}
protected readonly IDictionary<Type, Func<object>> getters;
protected object Get(Type type)
{
var types = type.GetGenericArguments();
return getters[types[0]]();
}
public virtual A Get()
{
return (A) Get(this.GetType());
}
}
class DerivedGetter<B, A> : BaseGetter<A>, IGetThing<B>
where B : new() where A : new()
{
public DerivedGetter()
{
getters.Add(typeof(B), () => new B());
}
B IGetThing<B>.Get()
{
return (B) Get(this.GetType());
}
}
class Derived2Getter<C, B, A> : DerivedGetter<B, A>, IGetThing<C>
where C : new() where B : new() where A : new()
{
public Derived2Getter()
{
getters.Add(typeof(C), () => new C());
}
C IGetThing<C>.Get()
{
return (C) Get(this.GetType());
}
}
class Aa { }
class Bb : Aa { }
class Cc : Bb { }
class Dd { }
Use of methods (same as before!): var a = new DerivedGetter(); Console.WriteLine(a.Get() is Bb); var b = (IGetThing)a; Console.WriteLine(b.Get() is Bb);
var c = new Derived2Getter<Cc, Bb, Aa>();
Console.WriteLine(c.Get() is Cc);
var d = (IGetThing<Bb>)c;
Console.WriteLine(d.Get() is Cc);
var e = (IGetThing<Aa>)c;
Console.WriteLine(e.Get() is Cc);
var f = new DerivedGetter<Dd, Aa>();
Output:
True
True
True
True
True
Unhandled Exception: System.ArgumentException:
ConsoleApplication16.Dd doesn't inherit from
ConsoleApplication16.Aa
Old implementation below.
I don't think you can do this with the (just) type system. You have to implement both interfaces, either through the base class, or the derived class.
With that in mind, I may consider approaching this problem with injecting in the behavior you want as a protected member to the base class.
Something like this: interface IGetThing { T Get(); }
class BaseGetter<A> : IGetThing<A> where A : new()
{
protected IGetThing<A> Getter { get; set; }
public virtual A Get()
{
return Getter == null ? new A() : Getter.Get();
}
}
class DerivedGetter<B, A> : BaseGetter<A>, IGetThing<B> where B : A, new() where A : new()
{
public DerivedGetter()
{
Getter = this;
}
public override A Get()
{
return new B();
}
B IGetThing<B>.Get()
{
return (B) Get();
}
}
class Aa { }
class Bb : Aa { }
When ran,
var a = new DerivedGetter<Bb, Aa>();
Console.WriteLine(a.Get() is Bb);
var b = (IGetThing<Aa>)a;
Console.WriteLine(b.Get() is Bb);
outputs:
True
True
Upvotes: 2