Пытается использовать комбинацию общих параметров

Это сбивает с толку, поскольку я получаю кажущиеся противоречивые ошибки.

Я использую generics, ограничивая T до Something, затем сдерживая U до AnOperation<Something>.

Я ожидал, что объект AnOperation<Something> теперь рассматривается как тип U. Но я получаю ошибки:

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

Это странно. Ну, я попытался явным образом передать его U, тогда я получил эту ошибку:

Cannot convert type 'ConsoleApp1.AnOperation<T>' to 'U', который также заявил, что 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
    {
    }

}

Что здесь происходит?

Изменить: я пытаюсь понять, в чем проблема на уровне языка, не ища обходного пути по реальной проблеме.

Ответ 1

Вы почти поняли это со своим ограничением, но не совсем. Вы определяете

 where U : AnOperation<Something>

Но тогда вы создаете

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

Это не одно и то же. Если вы измените свое ограничение на...

 where U : AnOperation<T>

... со мной все будет хорошо.

Другая проблема заключается в том, что, хотя каждый U является AnOperation<T>, не каждый AnOperation<T> является U. Когда вы объявляете...

public U GetAnOperationOfSomething()

... вы делаете гарантию того, что возвращаемый метод будет U. AnOperation<T> не может удовлетворить эту гарантию.

Вы решаете это с помощью typecast на U. Это противоречит назначению вашего общего класса, поскольку каждый U должен быть AnOperation<T> или вы получите исключение во время выполнения. Это делает ненужным весь параметр типа U. То, что вы действительно хотите сделать, это создать U. Вы можете использовать ограничение new() для этого:

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

Ограничение new() гарантирует, что U будет иметь общедоступный конструктор по умолчанию, который вы можете вызвать.

Ответ 2

T в вашем классе есть любой класс, наследующий от Something. U - это любой класс, наследующий от AnOperation<Something>. Предположим, что у вас есть дочерний класс:

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

Теперь ваш U может быть ChildOperation<Something>, и вы не можете вернуть экземпляр родительского класса (AnOperation<T>) в качестве экземпляра дочернего класса (U, который равен ChildOperation<Something>). Это может также пойти не так, когда T действительно ChildSomething, потому что AnOperation<Something> не может быть неявно преобразован в AnOperation<ChildSomething>. Короче говоря - AnOperation<T> действительно не может быть всегда преобразован в ваш тип U, поэтому компилятор прав.

Ответ 3

Просто для поддержки ответа @Evk. См. Этот пример.

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
{
}

Ответ 4

Это удивительно компилируется, если вы переключитесь с интерфейса AnOperation на IAnOperation:

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
{ }

Ответ 5

Не понятно, что вы пытаетесь сделать, вы можете расширить вопрос. Например, будет ли несколько видов операций? В зависимости от этого существует несколько возможных решений

Если подтипов операции нет, параметр U не нужен. Просто верните AnOperation<T>, поскольку он уже может описать все операции.

    // 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
        {
        }
    }

Если существует много видов операций (что кажется вероятным), проблема заключается в том, что ваш класс не может знать, как их создавать, когда он не знает о них. Вы должны дать вашему классу функцию, которая может привести к возврату экземпляра U.

    // 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
        {
        }

    }

В качестве альтернативы вы можете ограничить U новым new(), чтобы ваш класс мог создавать их без дополнительных знаний.

    // 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
        {
        }

    }

Ответ 6

Ну, для меня работает следующее (EDIT: я в рамке 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
    {
    }