Sharky
Sharky

Reputation: 6284

Trying to utilize combination of generic parameters

This is confusing, as I'm getting seemingly contradictive errors.

I'm using generics, constraining T to Something, then constraining U to AnOperation<Something>.

I expected that an object AnOperation<Something> is from now on considered of type U. But, I'm getting errors:

Cannot implicitly convert type 'ConsoleApp1.AnOperation<T>' to 'U'

That's weird. Well, i tried explicitly casting it to U, then I got this error:

Cannot convert type 'ConsoleApp1.AnOperation<T>' to 'U' which also stated Cast is redundant

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    class MyClass<T, U>
        where T : Something
        where U : AnOperation<Something>
    {
        public U GetAnOperationOfSomething()
        {
            AnOperation<T> anOperation = new AnOperation<T>();

            return anOperation; // Cannot implicitly convert type 'ConsoleApp1.AnOperation<T>' to 'U'

            // return (U)anOperation; // Cannot convert type 'ConsoleApp1.AnOperation<T>' to 'U' also Cast is redundant
        }
    }

    public class Something
    {
    }

    public class AnOperation<T>
        where T : Something
    {
    }

}

What's happening here?

Edit: I'm trying to understand what is the problem in the language level, not looking for a workaround on an actual problem.

Upvotes: 13

Views: 1143

Answers (6)

Sefe
Sefe

Reputation: 14007

You almost got it right with your constraint, but not quite. You define

 where U : AnOperation<Something>

But then you create

AnOperation<T> anOperation = new AnOperation<T>()

That is not the same thing. If you change your constraint to...

 where U : AnOperation<T>

...you will be fine.

Another problem is that while every U is an AnOperation<T>, not every AnOperation<T> is an U. When you declare...

public U GetAnOperationOfSomething()

...you are making the guarantee that what the method returns is an U. AnOperation<T> can not satisfy that guarantee.

You are solving this with a typecast to U. That is against the purpose of your generic class, since every U must be an AnOperation<T> or you will get a runtime exception. That makes the whole type parameter U unnecessary. What you actually want to do is create an U. You can use the new() constraint for that:

class MyClass<T, U>
    where T : Something
    where U : AnOperation<T>, new()
{
    public U GetAnOperationOfSomething()
    {
        U anOperation = new U();
        //...
        return anOperation;
    }
}

The new() constraint guarantees that U will have a public default constructor, which you can invoke.

Upvotes: 11

astef
astef

Reputation: 9498

It surprisingly compiles if you switch from AnOperation class to IAnOperation interface:

class MyClass<T, U>
    where T : Something
    where U : IAnOperation<Something>
{
    public U GetAnOperationOfSomething()
    {
        IAnOperation<T> anOperation = GenAnOperation();
        return (U)anOperation;
    }

    private IAnOperation<T> GenAnOperation()
    {
        throw new NotImplementedException();
    }
}

public class Something
{ }

public interface IAnOperation<T>
    where T : Something
{ }

Upvotes: 1

Anders Forsgren
Anders Forsgren

Reputation: 11101

It's not clear what you are trying to do, you may want to expand the question. For instance, will there be several types of operation? Depending on that there are several possible solutions

If there are no operation subtypes, the U parameter is unnecessary. Just return AnOperation<T> as that can already describe all operations.

    // With only one type of operation
    namespace ConsoleApp2
    {    

        class MyClass<T> where T : Something        
        {
            public AnOperation<T> GetAnOperationOfSomething()
            {
                AnOperation<T> anOperation = new AnOperation<T>();
                return anOperation; 
            }
        }

        public class Something
        {
        }

        public sealed class AnOperation<T>
            where T : Something
        {
        }
    }

If there are many kinds of operations (which seems likely), then the problem is that your class can't know about how to make them when it doesn't know about them. You need to give your class a function that can produce an U instance to return.

    // With a factory for operations
    namespace ConsoleApp1
    {    
        class MyClass<T, U>
            where T : Something
            where U : AnOperation<Something>
        {
            private readonly Func<T, U> operationMaker;

            public MyClass(Func<T, U> operationMaker)
            {
                this.operationMaker = operationMaker;           
            }

            public U GetAnOperationOfSomething(T something)
            {           
                U anOperation = operationMaker(something);
                return anOperation; 
            }
        }

        public class Something
        {
        }

        public class AnOperation<T>
            where T : Something
        {
        }

    }

Alternatively you can constrain U to new new() so that your class can create them without further knowledge.

    // With a "new()" contraint on U
    namespace ConsoleApp3
    {    
        class MyClass<T, U>
            where T : Something
            where U : AnOperation<Something>, new()
        {
            public U GetAnOperationOfSomething()
            {           
                U anOperation = new U();
                return anOperation; 
            }
        }

        public class Something
        {
        }

        public class AnOperation<T>
            where T : Something
        {
        }

    }

Upvotes: 1

jegtugado
jegtugado

Reputation: 5141

Just to support @Evk's answer. See this example.

class Program
{
    static void Main(string[] args)
    {
        MyClass<SomeSomething, AnOperation<Something>> foo = 
        new MyClass<SomeSomething, AnOperation<Something>>(); 
        // AnOperation<SomeSomething> will cause a compile error.

        var bar = foo.GetAnOperationOfSomething();

        Console.WriteLine(bar != null);

        Console.Read();
    }
}

class MyClass<T, U>
    where T : Something
    where U : AnOperation<Something>
{
    public U GetAnOperationOfSomething()
    {
        U anOperation = Activator.CreateInstance<U>();

        return anOperation;
    }
}

public class Something
{
}

public class AnOperation<T>
    where T : Something
{
}

public class SomeSomething : Something
{
}

Upvotes: 1

Evk
Evk

Reputation: 101483

T in your class is any class inheriting from Something. U is any class inheriting from AnOperation<Something>. Suppose you have child class like this:

public class ChildOperation<T> : AnOperation<T> {}

Now your U might be ChildOperation<Something>, and you cannot return instance of parent class (AnOperation<T>) as instance of child class (U which is ChildOperation<Something>). It might also go wrong when T is really ChildSomething, because AnOperation<Something> cannot be implicitly converted to AnOperation<ChildSomething>. Long story short - AnOperation<T> indeed cannot be always converted to your U type, so compiler is correct.

Upvotes: 5

Prateek Shrivastava
Prateek Shrivastava

Reputation: 1937

Well the following works for me (EDIT: I am in framework v4.5.2):

class Program
    {
        static void Main(string[] args)
        {
            MyClass<Something, AnOperation<Something>> obj = new MyClass<Something, AnOperation<Something>>();
        }
    }

    class MyClass<T, U>
        where T : Something
        where U : AnOperation<Something>
    {
        public U GetAnOperationOfSomething()
        {
            AnOperation<T> anOperation = new AnOperation<T>();

            return anOperation as U;

        }
    }

    public class Something
    {
    }

    public class AnOperation<T>
        where T : Something
    {
    }

Upvotes: 0

Related Questions