Как понизить экземпляр универсального типа?

Я борюсь с тем, как правильно использовать C# Generics. В частности, я хочу иметь метод, который принимает универсальный тип в качестве параметра и выполняет разные действия в зависимости от типа универсального. Но я не могу «унизить» общий тип. См. пример ниже.

Компилятор жалуется на приведение (Bar<Foo>), говоря: «Невозможно преобразовать тип Bar<T> в Bar<Foo>». Но во время выполнения приведение в порядке, так как я проверил тип.

public class Foo { }

public class Bar<T> { }

// wraps a Bar of generic type Foo
public class FooBar<T> where T : Foo
{

    Bar<T> bar;

    public FooBar(Bar<T> bar)
    {
        this.bar = bar;
    }

}

public class Thing
{
    public object getTheRightType<T>(Bar<T> bar)
    {
        if (typeof(T) == typeof(Foo))
        {
            return new FooBar<Foo>( (Bar<Foo>) bar);  // won't compile cast
        }
        else
        {
            return bar;
        }
    }
}

person Mark Hansen    schedule 30.10.2012    source источник
comment
Это сработает, если вы пойдете return new FooBar<Foo>(bar as Bar<Foo>)?   -  person Corey Adler    schedule 30.10.2012
comment
Одним из преимуществ использования дженериков является безопасность типов, но вы теряете некоторую безопасность типов, возвращая object вместо более конкретного типа. Другое преимущество обобщений заключается в том, что один и тот же код можно использовать для нескольких типов, но вы теряете часть этого преимущества, разветвляя реализацию на основе типа аргумента. Я не знаю, как выглядит реальная программа, но, возможно, это показатель того, что возможна лучшая структура программы. Каково назначение функции getTheRightType? И как используется результат?   -  person Mike    schedule 30.10.2012
comment
Хороший вопрос, Майк. Вышеприведенный пример — просто упрощенный пример, абстрагированный от реального кода. Настоящий код относится к построению электронных таблиц и классу Worksheet‹I,F›, где I и F — разные типы структур данных. Рабочий лист отображается по-разному в зависимости от типов I и F во время выполнения. Я должен потратить еще немного времени на шаблоны и попытаться улучшить структуру программы. Но, сроки, сроки...   -  person Mark Hansen    schedule 30.10.2012


Ответы (2)


Компилятор не может знать, что Bar<T> может быть преобразовано в Bar<Foo> в этом случае, потому что обычно это неверно. Вы должны «обмануть», введя приведение к object между ними:

return new FooBar<Foo>( (Bar<Foo>)(object) bar);
person Thomas Levesque    schedule 30.10.2012
comment
Фактические рассуждения однажды объяснил Эрик Липперт. Это было немного сложнее. В конце концов, компилятор может просто выдать код, как в вашем примере. Ничто в принципе не противостоит этому. - person usr; 30.10.2012
comment
@usr, да, я помню, что читал что-то подобное в блоге Эрика, но не могу найти точную ссылку... - person Thomas Levesque; 30.10.2012
comment
Спасибо! Никогда бы не подумал, что это сработает. Я привык к Java, где вы можете выполнять приведение вниз во время компиляции, но получать исключение во время выполнения, если приведение не выполняется. - person Mark Hansen; 30.10.2012

Это должно сработать, и вам не придется сначала выполнять приведение к object.

public class Thing
{
    public object getTheRightType<T>(Bar<T> bar)
    {
        if (typeof(T) == typeof(Foo))
        {
            return new FooBar<Foo>(bar as Bar<Foo>);  // will compile cast
        }
        else
        {
            return bar;
        }
    }
}
person Josh C.    schedule 30.10.2012
comment
Это работает для меня, пока я добавляю ограничение класса where T : к определению функции. - person Josh; 06.02.2014