кастинг с наследованием интерфейса - PullRequest
0 голосов
/ 18 апреля 2019

Я пытаюсь использовать интерфейс вместо конкретной реализации, и при попытке вызвать метод Get() из интерфейса А

я сталкиваюсь с проблемой проектирования с кодом ниже.
  • classA реализует interfaceA
  • interfaceB наследуется от interfaceA
  • classB наследуется от classA и реализует interfaceB

Я знаю, как кастовать, как и т.д. ... но это приемлемо? Это лучшая практика? Просто интересно, есть ли другие способы справиться с такими сценариями. Thx

class Program
{
    static void Main(string[] args)
    {            
        interfaceA a = new classA();
        interfaceB b = a.Get(); // compilation error
        interfaceB bb = a.Get() as interfaceB; // Ok
    }
}

interface interfaceA
{
    interfaceA Get();
}

class classA : interfaceA
{
    interfaceA ia;

    public classA()
    {
        ia = new classA();
    }

    public interfaceA Get()
    {
        return ia;
    }
}

interface interfaceB : interfaceA
{

}

class classB : classA, interfaceB
{

}

Ответы [ 3 ]

1 голос
/ 18 апреля 2019

Проблема, с которой вы столкнулись, заключается в том, что a.Get () возвращает интерфейс A, и хотя интерфейс B можно назначить из интерфейса A, интерфейс A НЕ может быть назначен из интерфейса B. Таким образом, хотя classB наследуется от classA, ему все равно необходимо правильно реализовать interfaceB, который будет иметь несколько других методов / свойств, чем interfaceA. Вам нужно использовать более абстрактный класс, чтобы назначить более конкретный. например interfaceA a = new classB();, поскольку у класса B гарантированно есть все свойства методов interfaceA. Я надеюсь, что это имеет смысл.

0 голосов
/ 19 апреля 2019

Ошибка компиляции здесь:

interfaceB b = a.Get(); // compilation error
               ^^ return value is cast as interfaceA.

... потому что метод возвращает объект, приведенный как interfaceA, и пытается привести его как interfaceB. Из-за наследования все, что реализует interfaceB, также реализует interfaceA. Но обратное не верно.

Например, ClassA реализует interfaceA, но не реализует interfaceB. Так что вы не можете сделать это:

interfaceB b = new ClassA();

По той же причине вы получите ту же ошибку компилятора.

Это верно, даже если ваш метод выглядел так:

public interfaceA Get()
{
    return new ClassB();
}

Этот метод будет компилироваться, потому что ClassB реализует interfaceA.

Но это все равно не скомпилируется:

interfaceB b = a.Get(); // compilation error

... потому что результатом функции является cast as interfaceA. Другими словами, когда вы вызываете этот метод, единственное, что знает вызывающий объект, - это то, что он получает interfaceA. Он не знает, что такое конкретный класс. Это может или не может быть то, что реализует interfaceB.

Ошибка компилятора заставляет вас вычислять все это заранее, вместо того, чтобы запускать программу, а затем получать ошибку. Это потрясающе. Мы хотим этого. Гораздо сложнее выяснить проблему во время работы программы. Намного лучше, если компилятор покажет нам проблему.

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

Если мы сделаем это, эта строка будет скомпилирована:

interfaceB b = (interfaceB)a.Get();
               ^^ whatever this method returns, cast it as interfaceB.

// just like this one compiles
interfaceB bb = a.Get() as interfaceB; // Ok

Это замечательно, но когда мы запустим его, мы получим исключение , если возвращенный объект на самом деле не реализует interfaceB. И это будет исключением, которого мы могли бы избежать, если бы мы позволили компилятору выполнить свою работу и предупредили нас.

Решение Там не только один. Или, можно сказать, может быть один, но он специфичен для кода, который вы пишете прямо сейчас, а не что-то общее. Но вот несколько советов:

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

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

ISomeInterface GiveMeAnObject() 
{
    return new ThingThatImplementsTheInterface(); 
}

... единственное, что нас волнует, когда мы получаем этот объект, это то, что он реализует ISomeInterface. Мы вообще не хотим ничего знать о конкретном классе, других классах, которые реализуют интерфейс, или других интерфейсах, которые наследуются от ISomeInterface. Наш код не должен заботиться об этом. Основная цель состоит в том, чтобы избежать необходимости знать или заботиться об этих деталях. Если мы знаем или заботимся об этих деталях и обнаруживаем, что говорим: «Я знаю, что мой ISomeInterface является действительно экземпляром этого класса или этого класса», и мы разыгрываем его, то что-то пошло не так .

Одним из возможных исключений является downcasting . В некоторых случаях это может иметь смысл:

public interface ISomeInterface {}
public interface ISomeInheritedInterface : ISomeInterface {}

public ISomeInheritedInterface GetSomething()
{
    return new ClassThatImplementsInheritedInterface();
}

ISomeInterface foo = GetSomething();

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

Кроме этого, большинство наших обычных решений обычных проблем не должны включать в себя кастинг. Когда бы ни было возможно - что почти всегда - нам не нужно этого делать. Если мы хотим отслеживать все типы самостоятельно, а не позволять компилятору делать это, мы можем просто объявить каждую переменную и каждую функцию как object, а затем привести все. Это было бы кошмаром и усложнило бы программирование. (Или мы могли бы программировать в VB.NET с выключенным Option Strict, что в значительной степени одно и то же.)

0 голосов
/ 18 апреля 2019

В реальном случае я не вызываю конструктор из classA.Вместо этого я использую метод, который принимает объект типа InterfaceA, который еще не создан, который создается с правильным типом (classB).Реальная проблема, когда я пытаюсь вызвать метод из classA, который возвращает interfaceA для установки объекта interfaceB.

interfaceB bb = a.Get ();

Кодвыглядит так:

Insert(root, i);

private void Insert(interfaceB<T> node, T newItem)
{
    interfaceB<T> current = node;
    if (root == null)
    {
        interfaceB<T> newNode = new classB<T>(newItem);
        root = newNode;
        return;
    }
    // some code
    current = current.GetLeft(); // error since GetLeft() return a interfaceA<T>
    // some code 
}

Надеюсь, это хорошо поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...