4

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.

Community
  • 1
  • 1
Serge
  • 1,974
  • 6
  • 21
  • 33

2 Answers2

2

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
jdphenix
  • 15,022
  • 3
  • 41
  • 74
  • Fixed for that case. – jdphenix Feb 10 '16 at 01:02
  • A viable solution given the case above, but it doesn't look like it would scale for a 3rd (or further) level of derivation. E.g. Derived2Getter where C : B, casting to DerivedGetter, attempting to do a Get() to return a C type boxed as B. – Serge Feb 10 '16 at 01:39
  • Well, scaling beyond wasn't an original part of your question, though a good thought. Let me think about it a bit and get back to you. If you wouldn't mind, could you update your question with that new information? Or perhaps explaining the use case that you have here, since it's a little convoluted to begin with. I think there might be some X-Y problem going on. – jdphenix Feb 10 '16 at 01:44
  • Supposing that nobody else comes up with a very simple "duh" solution within the next day (which I highly doubt), this will be marked as accepted. I assure you, this is 100% likely an XY problem. I'm attempting to define a strict `ICopyable` interface (X referring to the implementing class itself, methods `X Copy()` and `void CopyMemebers(X other)` ), and the `Copy()` method is falling into the above pattern. XY aside, the actual question of whether this could be done syntactically without reflection still made me question, since its not really an unreasonable situation IMO. – Serge Feb 10 '16 at 03:42
  • In the C# spec 13.4.2, "• If any possible constructed type created from C would, after type arguments are substituted into L, cause two interfaces in L to be identical, then the declaration of C is invalid. **Constraint declarations are not considered when determining all possible constructed types.**" (emphasis mine) - that's why you're running into trouble here. – jdphenix Feb 10 '16 at 04:28
0

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
Serge
  • 1,974
  • 6
  • 21
  • 33