Пытается использовать комбинацию общих параметров
Это сбивает с толку, поскольку я получаю кажущиеся противоречивые ошибки.
Я использую 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
{
}