Правила обобщения и ограничения типа

просто из любопытства, почему компилятор обрабатывает неограниченный общий тип иначе, чем typeof(object)?

class Bar { }

class Foo
{
    void foo(object thing)
    {
        ((Bar)thing).ToString();
    }
}

class Foo<T>
{
    void foo(T thing)
    {
        ((Bar)thing).ToString();
    }
}

в приведенном выше, приведение "t вещь" в бар приводит к ошибке компилятора. Однако кастинг "объектной вещи" на бар-это то, что компилятор позволяет мне делать, на мой собственный риск, конечно.

чего я не вижу, так это почему. В .net object в конце концов является catch-all, а тип времени выполнения может быть коробочным значением или объектом любого типа. Так Что Я ... не вижу логических причин для компилятора, чтобы различать эти два случая. Лучшее, что я могу сделать, это что-то вроде "программист ожидал бы, что компилятор выполнит проверку типов с универсальными типами, но не с объектом". :) Это все, что нужно?

кстати, я знаю, что я все еще могу получить свой бросок в случае Foo, просто написав

((Bar)(object)thing).ToString();

Я просто хочу понять, почему компилятор делает это...

2 ответов


значение здесь object. Если первый пример был все, кроме object он будет вести себя так же. В основном, то, что вы говорите в данный момент здесь:

(Bar)thing

это: "преобразовать T до Bar"; что в общем случае далеко не законно. Путем добавления object вы делаете это:

(Bar)(object)thing

что "преобразовать T до object...- ...что всегда законно, поскольку ... --3--> является корнем всех управляемых типов; и обратите внимание, это может invove коробку - "...а затем бросьте object как Bar" - опять же; это всегда законно во время компиляции и включает проверку типа ("unbox-any") во время выполнения.

например: предположим, что T is DateTime...

DateTime thing = ...
Bar bar = (Bar)(object)thing;

вполне допустимо, конечно, это не во время выполнения, но это сценарий, нужно иметь в виду.


это сводится к семантике и цели создания дженериков. Если у вас есть общий тип T, компилятор не позволит вам произвольно привести его непосредственно к любому другому объекту. Это имеет смысл, поскольку цель T-заставить программиста указать, какой тип T на самом деле есть. Это не будет "объект", это будет определенный тип объекта. Во время компиляции компилятор не может знать, что будет в T, и поэтому не может бросить его.

литье из объектных работ поскольку это анонимный объект-как противоположность известному типу объекта, который определяется в его использовании.

Это может быть расширено с помощью предложения "where". Например, вы можете указать, что T должен иметь тип IBar;

interface IBar { }

class Bar : IBar { }

class Foo<T>
    where T : IBar
{
    void foo(T thing)
    {
        ((IBar)thing).ToString();
    }
}

наследование также работает с предложением where;

class Bar { }

class Foo<T>
    where T : Bar
{
    void foo(T thing)
    {
        // now you don't need to cast at all as the compiler knows
        // exactly what type T is (at a parent level at least)
        thing.ToString();
    }
}