Serge
Serge

Reputation: 2074

C# Override and New At Same Time

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

Answers (2)

Serge
Serge

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

jdphenix
jdphenix

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

Related Questions