amura.cxg
amura.cxg

Reputation: 2427

C# Inheritance with Generics and the dynamic keyword

The problem I'm actually working on is related to mappers in ASP.NET MVC but that's way too complex to post on SO, so I've simplified the issue I'm having below. I'll post my code first as it's easier to explain what I'm trying to achieve after the code.

Supporting Code

public abstract class BaseFoo
{
    public int CommonProperty { get; set; }
}

public class Foo1 : BaseFoo
{
    public int SomeProperty { get; set; }
}

public class Foo2 : BaseFoo
{
    public int AnotherProperty { get; set; }
}

public interface IMyInterface<T>
{
    void SomeMethod(T t);
}

public abstract class BaseClass<T> : IMyInterface<T>
    where T : BaseFoo
{
    public virtual void SomeMethod(T t)
    {
        t.CommonProperty = 1;
    }
}

public class ConcreteClass1 : BaseClass<Foo1>
{
    public override void SomeMethod(Foo1 t)
    {
        t.SomeProperty = 57;
        base.SomeMethod(t);
    }
}

public class ConcreteClass2 : BaseClass<Foo2>
{
    public override void SomeMethod(Foo2 t)
    {
        t.AnotherProperty = 123;
        base.SomeMethod(t);
    }
}

public static class ConcreteClassFactory
{
    public enum ConcreteClassType
    {
        ConcreteClass1,
        ConcreteClass2
    }

    public static dynamic CreateClass(ConcreteClassType type)
    {
        dynamic toReturn = null;

        switch (type)
        {
            case ConcreteClassType.ConcreteClass1:
                toReturn = new ConcreteClass1();
                break;
            case ConcreteClassType.ConcreteClass2:
                toReturn = new ConcreteClass2();
                break;
            default:
                break;
        }

        return toReturn;
    }
}

What I want to do is dynamically create different ConcreteClasss and call SomeMethod on that created object, basically I want to pass around my ConcreteClasss as BaseClass, much like you can pass around Foos as BaseFoo. I've gotten it to work with the following code:

class Program
{
    static void Main(string[] args)
    {
        BaseFoo foo = new Foo1();

        dynamic bar = ConcreteClassFactory.CreateClass(ConcreteClassFactory.ConcreteClassType.ConcreteClass1);

        bar.SomeMethod(foo as dynamic);
    }
}

However, this seems very kludgy to cast to a dynamic (also I don't fully understand why removing as dynamic throws a RuntimeBinderException, if someone can explain what's going on that would be appreciated). Is there a better way to achieve what I'm trying to do here?

Upvotes: 0

Views: 75

Answers (1)

Scott Chamberlain
Scott Chamberlain

Reputation: 127563

With the constraints you have what I would do would be to cast and throw errors from inside the overridden SomeMethod.

public abstract class BaseClass : IMyInterface<BaseFoo>
{
    public virtual void SomeMethod(BaseFoo t)
    {
        t.CommonProperty = 1;
    }
}

public class ConcreteClass1 : BaseClass
{
    public override void SomeMethod(BaseFoo t)
    {
        if(t == null)
            throw new ArgumentNullException(nameof(t));

        var foo1 = t as Foo1;
        if(foo1 == null)
            throw new NotSupportedException($"{nameof(ConcreteClass1)} does not support types other than {nameof(Foo1)}");

        foo1.SomeProperty = 57;
        base.SomeMethod(foo1);
    }
}

public class ConcreteClass2 : BaseClass
{
    public override void SomeMethod(BaseFoo t)
    {
        if (t == null)
            throw new ArgumentNullException(nameof(t));

        var foo2 = t as Foo2;
        if (foo2 == null)
            throw new NotSupportedException($"{nameof(ConcreteClass2)} does not support types other than {nameof(Foo2)}");

        foo2.AnotherProperty = 123;
        base.SomeMethod(foo2);
    }
}

public static class ConcreteClassFactory
{
    public enum ConcreteClassType
    {
        ConcreteClass1,
        ConcreteClass2
    }

    public static BaseClass CreateClass(ConcreteClassType type)
    {
        BaseClass toReturn = null;

        switch (type)
        {
            case ConcreteClassType.ConcreteClass1:
                toReturn = new ConcreteClass1();
                break;
            case ConcreteClassType.ConcreteClass2:
                toReturn = new ConcreteClass2();
                break;
            default:
                break;
        }

        return toReturn;
    }
}

Used like

class Program
{
    static void Main(string[] args)
    {
        BaseFoo foo = new Foo1();

        var bar = ConcreteClassFactory.CreateClass(ConcreteClassFactory.ConcreteClassType.ConcreteClass1);

        bar.SomeMethod(foo);
    }
}

Upvotes: 1

Related Questions