Alpha75
Alpha75

Reputation: 2280

Casting generic types to subclass

I'm starting with generics types and I'm stuck with my project. Maybe I did not understand generics very well. Explanations have been inserted inline. Basically I need to implement the Do() method but I don't know how to resolve <T2>:

public abstract class MyGenericClass<T> { }

public class MyGenericClass<T, T2> : MyGenericClass<T>
{
    public Expression<Func<T, T2>> expression;

    public MyGenericClass(Expression<Func<T, T2>> expression)
    {
        this.expression = expression;
    }
}

public class MyClass<T>
{
    // I need to mantain a list of my generic class for later use.
    // I don't know T2 at this point.
    // So I Chose to use Inheritance as a workaround (MyGenericClass<T> and MyGenericClass<T, T2>).
    // Maybe it's not a good solution but I counldn't find out other solution.
    public List<MyGenericClass<T>> MyGenericList = new List<MyGenericClass<T>>();

    // I receive the parametric argument T2 here as part of an Expresion.
    // And I keep the expression in my list.
    public MyGenericClass<T, T2> ReceivingMethod<T2>(Expression<Func<T, T2>> expression)
    {
        MyGenericClass<T, T2> genericImp = new MyGenericClass<T, T2>(expression);
        MyGenericList.Add(genericImp);
        return genericImp;
    }
}

public class Client<T>
{
    MyClass<T> class1;

    // class1 has been created and his field MyGenericList has been populated.
    // Then when I call Do()....
    public void Do()
    {
        foreach (var item in class1.MyGenericList)
        {
            // I need something like this here, 
            // but it does not compile because I don't know T2 here.
            // The caller of Do() method doesn't know T2.
            MyGenericClass<T, T2> myGenericItem = (MyGenericClass<T, T2>)item;
            var a = myGenericItem.expression;
        }
    }
}

Upvotes: 1

Views: 333

Answers (2)

Alpha75
Alpha75

Reputation: 2280

Solution 1 Inspired from @KMoussa comment. I've delegate the responsability to MyGenericClass using an abstract method. This seems a better design. All subclasses will implement this method DoTheWork(). And can be invoked from my Client.Do() method with only T param:

public abstract class MyGenericClass<T>
{
    public abstract string DoTheWork();
}

public class MyGenericClass<T, T2> : MyGenericClass<T>
{
    public override string DoTheWork()
    {
        // I can use expression here
    }

    private Expression<Func<T, T2>> expression { get; set; }

    public MyGenericClass(Expression<Func<T, T2>> expression)
    {
        this.expression = expression;
    }
}

public class MyClass<T>
{
    public List<MyGenericClass<T>> MyGenericList = new List<MyGenericClass<T>>();
    public void ReceivingMethod<T2>(Expression<Func<T, T2>> expression)
    {
        MyGenericClass<T, T2> genericImp = new MyGenericClass<T, T2>(expression);
    }
}

public class Client<T>
{
    MyClass<T> class1;
    public void Do()
    {
        // I don't need to cast to MyGenericClass<T, T2>
        foreach (MyGenericClass<T> myGenericItem in class1.MyGenericList)
        {
                string result = myGenericItem.DoTheWork();
        }
    }
}

Solution 2 Inspired from @ja72 and @juharr comments. Using reflection. First I save the type T2 on MyGenericClass using an abstract property. Then I can invoke a generic method MethodWithArgument using reflection so I can introduce the parameter for the casting:

public abstract class MyGenericClass<T>
{
    public abstract Type type { get; set; }
}

public class MyGenericClass<T, T2> : MyGenericClass<T>
{
    public Expression<Func<T, T2>> expression { get; set; }

    public MyGenericClass(Expression<Func<T, T2>> expression)
    {
        type = typeof(T2);  // I save the type of T2
        this.expression = expression;
    }
}

public class MyClass<T>
{
    public List<MyGenericClass<T>> MyGenericList = new List<MyGenericClass<T>>();
    public void ReceivingMethod<T2>(Expression<Func<T, T2>> expression)
    {
        MyGenericClass<T, T2> genericImp = new MyGenericClass<T, T2>(expression);
    }
}

public class Client<T>
{
    MyClass<T> class1;
    public void Do()
    {
        foreach (MyGenericClass<T> myGenericItem in class1.MyGenericList)
        {
            MethodInfo method = GetType().GetMethod("MethodWithArgument");
            MethodInfo generic = method.MakeGenericMethod(new Type[] { myGenericItem.type });
            string g = (string)generic.Invoke(this, new object[] { myGenericItem });
        }
    }

    // I introduce T2 in this method
    public string MethodWithArgument<T2>(MyGenericClass<T> myClass)
    {
        // Now, the casting is valid
        MyGenericClass<T, T2> mySubClass = (MyGenericClass<T, T2>)myClass;
        var a = mySubClass.expression;
        // I can work with expression here
    }
}

Upvotes: 0

John Alexiou
John Alexiou

Reputation: 29274

You have to give Do() the T2 parameter somehow. So my solution is to create a method parameter of the same type. I also nested the types in order to make sure all of them refer to the same T.

I also renamed the parameters to be more descriptive

//  T  -> TArg
//  T2 -> TResult
public abstract class MyBaseClass<TArg>
{
    public class MyExpressionClass<TResult> : MyBaseClass<TArg>
    {
        public Expression<Func<TArg, TResult>> Expression { get; private set; }
        public MyExpressionClass(Expression<Func<TArg, TResult>> expression)
        {
            this.Expression=expression;
        }
    }

    public class MyCollectionClass 
    {
        public List<MyBaseClass<TArg>> MyGenericList = new List<MyBaseClass<TArg>>();

        public MyExpressionClass<TResult> ReceivingMethod<TResult>(Expression<Func<TArg, TResult>> expression)
        {
            var genericImp = new MyExpressionClass<TResult>(expression);
            MyGenericList.Add(genericImp);
            return genericImp;
        }
    }

    public class Client
    {
        public MyCollectionClass List = new MyCollectionClass();

        public void Do<TResult>()
        {
            foreach(var item in List.MyGenericList)
            {
                var expr = item as MyExpressionClass<TResult>;
                if(expr!=null)
                {
                    var a = expr.Expression;
                    Console.WriteLine(a);
                }
            }
        }
    }
}



class Program
{
    static void Main(string[] args)
    {
        var client = new MyBaseClass<int>.Client();
        // add conversion expressions
        client.List.ReceivingMethod((i) => (i).ToString());
        client.List.ReceivingMethod((i) => (2*i).ToString());
        client.List.ReceivingMethod((i) => (3*i).ToString());

        // The programmer has to manually enforce the `string` type
        // below based on the results of the expressions above. There
        // is no way to enforce consistency because `TResult` can be 
        // _any_ type. 
        client.Do<string>();

        // Produces the following output
        //
        // i => i.ToString()
        // i => (2*i).ToString()
        // i => (3*i).ToString()
    }
}

Upvotes: 1

Related Questions