Концепции OOPS: В чем разница в передаче ссылки объекта на интерфейс и создании объекта класса в С#?

У меня есть класс, CustomerNew и интерфейс ICustomer:

public class CustomerNew : ICustomer
{
    public void A()
    {
        MessageBox.Show("Class method");
    }

    void ICustomer.A()
    {
        MessageBox.Show("Interface method");
    }


    public void B()
    {
        MessageBox.Show("Class Method");
    }
}

public interface ICustomer
{
    void A();
}

Я очень смущен между этими двумя кодами строк.

ICustomer objnew = new CustomerNew();
CustomerNew objCustomerNew = new CustomerNew();
objnew.B(); // Why this is wrong?
objCustomerNew.B(); // This is correct because we are using object of class

Первая строка кода означает, что мы передаем ссылку на объект класса CustomerNew в objnew, правильно ли? Если да, то почему я не могу получить доступ к методу B() класса с interface objnew?

Может кто-нибудь объяснить эти два в деталях.

Ответ 1

На самом деле интерфейс также является типом (вы не можете создавать экземпляры интерфейсов, поскольку они являются только метаданными).

Так как CustomerNew реализует ICustomer, экземпляр CustomerNew может быть увеличен до ICustomer. Когда a CustomerNew вводится как ICustomer, вы можете получить доступ только к членам ICustomer.

Это связано с тем, что С# является строго типизированным языком, поэтому для доступа к определенному члену (т.е. методам, свойствам, событиям...) вам нужна ссылка на объект, которая должна быть квалифицирована типом, который определяет членов, которые вы хотите получить доступ (т.е. вам нужно сохранить объект CustomerNew в ссылке типа CustomerNew для доступа к методу B).

Update

OP сказал:

Поэтому из-за восходящего потока мы можем получить доступ только к тем методам, которые находятся внутри интерфейс, правильно? UPCASTING - главная причина этого?

Да. Легкое объяснение - это объект, который реализует ICustomer не обязательно должен быть CustomerNew. Вам нужно сжать ICustomer ссылку на CustomerNew, чтобы иметь доступ к CustomerNew членам.

Поскольку оба класса и интерфейсы являются типами, а С# является строго типизированным языком, вы получаете доступ к членам объекта, предоставляя его фактический тип. Вот почему вам нужно использовать броски.

Например, ваш код делает неявное повышение:

// This is an implicit cast that equivalent to
// ICustomer objnew = (ICustomer)new CustomerNew()
ICustomer objnew = new CustomerNew();

Неявные восходящие потоки возможны, потому что компилятор уже знает CustomerNew реализует метаданные ICustomer для интроспекции CustomerNew, в то время как вы не можете ссылаться на down , потому что кто знает, кто реализует ICustomer? Это может быть либо CustomerNew, либо любой другой класс или даже структура:

ICustomer asInterface = new CustomerNew();

// ERROR: This won't compile, you need to provide an EXPLICIT DOWNCAST
CustomerNew asClass1 = asInterface;

// OK. You're telling the compiler you know that asInterface
// reference is guaranteed to be a CustomerNew too!
CustomerNew asClass2 = (CustomerNew)asInterface;

Если вы не уверены, что ICustomer является CustomerNew, вы можете использовать оператор as, который не будет генерировать исключение во время выполнения, если трансляция невозможна:

// If asInterface isn't also a CustomerNew, the expression will set null
CustomerNew asClass3 = asInterface as CustomerNew;

Ответ 2

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

Позвольте мне привести вам пример. Рассмотрим машину для продажи соды. Он имеет слот или два для ввода монет, несколько кнопок для выбора правильного типа соды и кнопки для раздачи соды (если кнопка выбора также не делает этого).

Теперь это интерфейс. Это скрывает сложность машины за интерфейсом и предоставляет вам несколько вариантов.

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

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

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

Итак, вернемся к вашему коду.

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

Другая переменная была объявлена ​​как имеющая тип базового объекта, поэтому у вас есть полный доступ ко всем ее общедоступным функциям. Подумайте об этом, как разблокировать торговый автомат и использовать его с открытой передней панелью.

Ответ 3

В первой строке:

ICustomer objnew

Вы инструктируете компилятор обрабатывать objnew как ICustomer, и поскольку интерфейс не объявляет метод B(), он делает ошибки.

Во второй строке:

CustomerNew objCustomerNew

Вы имеете в виду objCustomerNew как CustomerNew, и поскольку он задает метод B(), он компилируется просто отлично.

Ответ 4

Это имеет все, что связано с проверкой типа времени компиляции (т.е. статическим). Если вы посмотрите на две строки

ICustomer objnew = new CustomerNew();
objnew.B(); 

вы можете ясно видеть, что объект, на который ссылается objnew, имеет метод B(), поэтому вы знаете, что во время выполнения во второй строке не будет проблем.

Но это не так, как компилятор смотрит на него. Компилятор использует набор довольно простых правил, чтобы выяснить, следует ли сообщать об ошибке. Когда он выглядит как вызов objnew.B(), он использует статический (т.е. Объявленный) тип objnew для определения типа получателя. Статический (объявленный) тип ICustomer, и этот тип не объявляет метод B(), поэтому сообщается об ошибке.

Итак, почему компилятор игнорирует начальное значение, заданное для ссылки? Вот две причины:

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

ICustomer objnew = new CustomerNew();
if( some complicated expression ) objnew = new CustomerOld() ;
objnew.B(); 

где CustomerOld - это некоторый класс, реализующий интерфейс, но не имеющий метода B().

Второе: не может быть никакого выражения инициализации. Это происходит, в частности, с параметрами. Когда компилятор компилирует метод, он не имеет доступа к набору всех вызовов метода. Рассмотрим

void f( ICustomer objnew ) {
    objnew.B(); 
}

Чтобы правила были простыми, те же правила используются для параметров, как и для (других) переменных.

Конечно, вы можете себе представить язык, где правила разные, но это то, как работают С#, Java и подобные языки.

Ответ 5

Ваша переменная objnew является ссылкой на объект, реализующий ICustomer. Имейте в виду, что это может быть любой объект, если он реализует ICustomer. Итак, все, что раскрывается этой ссылкой, являются членами ICustomer, который является только методом A() в вашем примере.

Если вы хотите получить доступ к методу B() объекта, на который ссылается objnew, вам нужно будет явно преобразовать его в ссылку CustomerNew (что безопасно на уровне С# на работе), например, что:

CustomerNew objCustomerNew = objnew as CustomerNew;
objCustomerNew.B();

или

(objnew as CustomerNew).B();

Имейте в виду, что objnew может быть любого типа, реализующего ICustomer, поэтому преобразование objnew as CustomerNew может быть разрешено для null, если позже вы реализуете другие классы ICustomer.

Ответ 6

В простых словах я бы сказал, что objnew - это переменная типа ICustomer, которая содержит ссылку типа CustomerNew. Поскольку ICustomer не имеет декларации или определения (которое не может иметь, поскольку это интерфейс) метода B(), который компилятор проверяет во время компиляции и он выдает ошибку типа компиляции.

Теперь, придя к объяснению, во время компиляции компилятор проверяет таблицу MethodDef Тип переменной i.e. ICustomer и поднимает уровень иерархии определения класса. Если он найдет метод в таблице определения, он будет ссылаться на найденный метод (который снова зависит от других сценариев, таких как переопределение, которые мы можем обсудить в другой теме). Это мы можем понять, используя простой пример, который я хочу обсудить здесь.

public interface IMyInterface
{

}

public class MyBaseClass:IMyInterface
{
    public string B()
    {
        return "In Abstract";
    }
}

public class MyDerivedClass : MyBaseClass
{

}

Просьба идти по иерархии классов в приведенном выше фрагменте кода. Теперь перейдем к части реализации, как показано ниже,

        MyBaseClass inst = new MyDerivedClass();
        inst.B(); //Works fine 

        MyDerivedClass inst1 = new MyDerivedClass();
        inst1.B(); //Works fine

        IMyInterface inst2 = new MyDerivedClass();
        inst2.B(); //Compile time error

Сценарий 1 работает отлично по очевидной причине, как я объяснил ранее, что inst является переменной типа MyBaseClass, которая содержит ссылку типа MyDerivedClass и MyBaseClass имеет определение метода B().

Теперь, придя к сценарию 2, он также отлично работает, но как? inst1 - это переменная типа MyDerivedClass, которая снова содержит переменную типа MyDerivedClass, но метод B() не указан в этом типе, но что компилятор делает, это проверяет во время компиляции таблицу MethodDef MyDerivedClass и находит, что MyBaseClass имеет определение, если метод B(), который он ссылается во время выполнения.

Итак, надеюсь, вы поняли, что объясняет, почему сценарий 3 не будет работать.