Вопрос

Возьмем следующее:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

Почему возникает приведенная выше ошибка во время компиляции?Это происходит с обоими ref и out аргументы.

Это было полезно?

Решение

=============

Обновить:Я использовал этот ответ в качестве основы для этой записи в блоге:

Почему параметры ref и out не допускают изменения типа?

Смотрите страницу блога для получения дополнительных комментариев по этому вопросу.Спасибо за отличный вопрос.

=============

Давайте предположим, что у вас есть занятия Animal, Mammal, Reptile, Giraffe, Turtle и Tiger, с очевидными отношениями подклассов.

Теперь предположим, что у вас есть метод void M(ref Mammal m). M умеет как читать, так и писать m.


Можете ли вы передать переменную типа Animal Для M?

Нет.Эта переменная может содержать Turtle, но M будем считать, что в нем содержатся только Млекопитающие.A Turtle не является Mammal.

Вывод 1: ref параметры нельзя сделать "больше".(Животных больше, чем млекопитающих, поэтому переменная становится "больше", потому что она может содержать больше объектов.)


Можете ли вы передать переменную типа Giraffe Для M?

Нет. M можете написать по адресу m, и M возможно, захочется написать Tiger в m.Теперь вы поставили Tiger в переменную, которая на самом деле имеет тип Giraffe.

Вывод 2: ref параметры нельзя сделать "меньше".


Теперь рассмотрим N(out Mammal n).

Можете ли вы передать переменную типа Giraffe Для N?

Нет. N можете написать по адресу n, и N возможно, захочется написать Tiger.

Вывод 3: out параметры нельзя сделать "меньше".


Можете ли вы передать переменную типа Animal Для N?

Хм.

Ну, а почему бы и нет? N не удается прочитать из n, он может писать только на него, верно?Вы пишете Tiger к переменной типа Animal и у вас все готово, не так ли?

Неправильно.Правило таково не "N могу писать только по адресу n".

Правила таковы, вкратце:

1) N должен написать по адресу n до того, как N возвращается нормально.(Если N броски, все ставки отменяются.)

2) N должен что-то написать, чтобы n прежде чем он прочитает что-нибудь из n.

Это допускает такую последовательность событий:

  • Объявить поле x типа Animal.
  • Пройти x в качестве out параметр для N.
  • N пишет Tiger в n, который является псевдонимом для x.
  • В другой теме кто-то пишет Turtle в x.
  • N попытки прочитать содержимое n, и обнаруживает Turtle в том, что он считает переменной типа Mammal.

Очевидно, что мы хотим сделать это незаконным.

Вывод 4: out параметры нельзя сделать "больше".


Окончательный вывод: Ни то , ни другое ref ни out параметры могут различаться по своим типам.Поступить иначе - значит нарушить проверяемую безопасность типов.

Если эти вопросы базовой теории типов вас интересуют, подумайте о том, чтобы прочитать моя серия о том, как ковариация и контравариантность работают в C # 4.0.

Другие советы

Потому что в обоих случаях вы должны иметь возможность присвоить значение параметру ref / out.

Если вы попытаетесь передать b в метод Foo2 в качестве ссылки, а в Foo2 вы попытаетесь использовать a = new A (), это будет недопустимо.
По той же причине, по которой вы не можете написать:

B b = new A();

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

Языки ООП, которые, по-видимому, делают это, оставаясь традиционно статически защищенными от типов, являются "мошенническими" (вставляют скрытые динамические проверки типов или требуют проверки ВСЕХ источников во время компиляции для проверки);фундаментальный выбор заключается в следующем:либо откажитесь от этой ковариации и примите недоумение практиков (как это делает C # здесь), либо переходите к подходу динамической типизации (как это сделал самый первый язык ООП, Smalltalk), либо переходите к неизменяемым данным (с одним назначением), как это делают функциональные языки (при неизменяемости вы можете поддерживать ковариацию, а также избегать других связанных головоломок, таких как тот факт, что у вас не может быть квадратного подкласса Rectangle в мире изменяемых данных).

Рассмотреть:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

Это нарушило бы типобезопасность

В то время как другие ответы кратко объяснили причины такого поведения, я думаю, что стоит упомянуть, что если вам действительно нужно что-то сделать такого рода, вы можете реализовать аналогичную функциональность, превратив Foo2 в универсальный метод, как таковой:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

Потому что предоставление Foo2 ref B приведет к повреждению объекта, потому что Foo2 знает, как заполнить A часть B .

Разве компилятор не говорит вам, что он хотел бы, чтобы вы явно приводили объект, чтобы он мог быть уверен, что вы знаете свои намерения?

Foo2(ref (A)b)

Имеет смысл с точки зрения безопасности, но я бы предпочел, чтобы компилятор выдавал предупреждение вместо ошибки, поскольку существуют законные применения полиморфных объектов, передаваемых по ссылке. например.

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

Это не скомпилируется, но сработает ли это?

Если вы используете практические примеры для своих типов, вы увидите это:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

И теперь у вас есть функция, которая принимает предка ( т.е. Object ):

void Foo2(ref Object connection) { }

Что может быть не так с этим?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

Вам только что удалось назначить Bitmap для вашего SqlConnection .

Это не хорошо.

<Ч>

Попробуйте снова с другими:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

Вы добавили OracleConnection поверх вашего SqlConnection .

В моем случае моя функция приняла объект, и я не смог ничего отправить, поэтому я просто сделал

object bla = myVar;
Foo(ref bla);

И это работает

Мой Foo находится в VB.NET, и он проверяет тип внутри и выполняет много логики

Я прошу прощения, если мой ответ дублируется, но другие были слишком длинными

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top