Зачем приводить к интерфейсу?

В книге Джесси Либерти «Программирование на C#» (стр. 142) он приводит пример приведения объекта к интерфейсу.

 interface IStorable
 {
    ...
 }

 public class Document : IStorable
 {
    ...
 }

 ...
 IStorable isDoc = (IStorable) doc;  
 ...

Какой в ​​этом смысл, особенно если класс объекта все равно реализует интерфейс?

EDIT1: Чтобы уточнить, меня интересует причина приведения (если есть), не причина реализации интерфейсов. Кроме того, книга является его первым изданием 2001 года (основанным на C#1, поэтому пример может быть неуместным для более поздних версий C#).

EDIT2: я добавил некоторый контекст в код


person eft    schedule 06.03.2009    source источник
comment
Моим первым предположением было явное определяемое пользователем преобразование, но на самом деле, согласно (6.4.1), вы не можете определить явное определяемое пользователем приведение к интерфейсу.   -  person Tamas Czinege    schedule 06.03.2009
comment
Я нашел аналогичный раздел в последней версии, но, похоже, он несколько отличается.   -  person Jon Skeet    schedule 06.03.2009
comment
@Jon - спасибо, что искал это   -  person eft    schedule 06.03.2009
comment
Вы имеете в виду, почему explicit приводит к интерфейсу, или вы имеете в виду, почему вообще нужно приводить к интерфейсу?   -  person Randolpho    schedule 06.03.2009
comment
Я имею в виду, почему приведение explicit и является ли оно необходимым/полезным/избыточным?   -  person eft    schedule 06.03.2009


Ответы (12)


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

public class DocBase
{
  public virtual void DoSomething()
  {

  }
}

public class Document : DocBase, IStorable
{
  public override void DoSomething()
  {
    // Some implementation
    base.DoSomething();
  }

  #region IStorable Members

  public void Store()
  {
    // Implement this one aswell..
    throw new NotImplementedException();
  }

  #endregion
}

public class Program
{
  static void Main()
  {
    DocBase doc = new Document();
    // Now you will need a cast to reach IStorable members
    IStorable storable = (IStorable)doc;
  }
}

public interface IStorable
{
  void Store();
}
person Jeroen Landheer    schedule 06.03.2009
comment
Спасибо, но это меня немного смущает. Зачем использовать базовый тип DocBase при создании объекта Document? - person eft; 06.03.2009
comment
Одной из причин может быть то, что вы используете фабрику для создания объектов, и она возвращает объекты как их базовые типы. - person Chetan S; 06.03.2009
comment
Причин может быть сколько угодно. В основном я вижу реализацию базовых классов, которые поставляются третьими сторонами и не имеют указанного интерфейса. В этом случае класс DocBase выходит из другой библиотеки и реализуется с интерфейсом в вашем собственном коде. - person Jeroen Landheer; 06.03.2009
comment
-1, не объясняет, почему приведение используется при назначении типа, известного во время компиляции, для реализации IStorable переменной IStorable вместо EDIT2. - person binki; 16.10.2013
comment
// Теперь вам понадобится приведение для доступа к элементам IStorable — это то, что написано в приведенном выше коде, но: нельзя ли по-прежнему обращаться к элементам Istorable с помощью следующего: doc.Store() ?? - person BKSpurgeon; 08.08.2015

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

person Tundey    schedule 06.03.2009
comment
Нельзя ли сделать IStorable isDoc = doc; если doc является экземпляром реализующего класса IStorable? Приведение кажется излишним, если только я не путаю Java и C#. (К сожалению, я давно не занимался С#). - person Devin Jeanpierre; 06.03.2009
comment
@Devin: Да, приведение является излишним, если doc объявлен как тип, реализующий интерфейс. - person Jon Skeet; 06.03.2009
comment
Разве сама ссылка не запрещает вам вызывать какие-либо методы вне интерфейса? - person Bill the Lizard; 06.03.2009

Если объект реализует интерфейс явно (public void IStorable.StoreThis(...)), это приведение — самый простой способ получить доступ к членам интерфейса.

person James Curran    schedule 06.03.2009
comment
есть ли другой способ получить доступ к явным членам интерфейса? - person Svish; 09.03.2009
comment
Это ЕДИНСТВЕННЫЙ способ получить доступ к явно реализованным членам интерфейса. Причина явной реализации заключается в том, что вы хотите сохранить чистоту класса, поскольку, возможно, реализация используется редко. - person Greg Gum; 25.10.2012

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

public interface IFoo
{
     void Display();
}
public interface IBar
{
     void Display();
}

public class MyClass : IFoo, IBar
{
    void IBar.Display()
    {
        Console.WriteLine("IBar implementation");
    }
    void IFoo.Display()
    {
        Console.WriteLine("IFoo implementation");
    }
}

public static void Main()
{
    MyClass c = new MyClass();
    IBar b = c as IBar;
    IFoo f = c as IFoo;
    b.Display();
    f.Display();
    Console.ReadLine();
}

Это будет отображать

Реализация IBar
Реализация IFoo

person Ramesh    schedule 06.03.2009

Довольно сложно сказать, не вдаваясь в контекст. Если переменная doc объявлена ​​как тип, реализующий интерфейс, то приведение будет избыточным.

Какую версию книги вы читаете? Если это "Программирование C# 3.0", я посмотрю сегодня вечером, когда буду дома.

РЕДАКТИРОВАТЬ: Как мы уже видели в ответах, здесь есть три потенциальных вопроса:

  • Зачем использовать утверждение, показанное в вопросе? (Ответ: вам не нужно, если doc имеет соответствующий тип времени компиляции)
  • Почему уместно явное приведение к реализованному интерфейсу или базовому классу? (Ответ: явная реализация интерфейса, как показано в другом ответе, а также для выбора менее конкретной перегрузки при передаче значения приведения в качестве аргумента.)
  • Зачем вообще использовать интерфейс? (Ответ: работа с типом интерфейса означает, что вы будете менее восприимчивы к изменениям в конкретном типе позже.)
person Jon Skeet    schedule 06.03.2009
comment
Приведение (IStorable) также способствует удобочитаемости, явно заявляя, что doc НЕ является объектом IStorable, а скорее чем-то, что может быть приведено как IStorable. Хотя он и излишен, он добавляет эту часть информации для тех, кто может читать код позже. - person DevinB; 06.03.2009
comment
Я не понимаю, насколько это полезная информация - на самом деле, это подразумевает (для меня), что переменная doc имеет тип, который требует проверки во время выполнения (что звучит так, как будто это не дело здесь). Это заблуждение. - person Jon Skeet; 06.03.2009
comment
(Ответ: вам не нужно, если документ имеет соответствующий тип времени компиляции). Всего одна буква, но она имеет огромное значение. - person BKSpurgeon; 08.08.2015

Объект doc может иметь тип, который явно реализует члены IStorable, не добавляя их в первичный интерфейс классов (т. е. их можно вызывать только через интерфейс).

На самом деле «приведение» (с использованием синтаксиса (T)) не имеет никакого смысла, поскольку С# автоматически обрабатывает восходящие преобразования (приведение к родительскому типу) (в отличие, например, от F#).

person Christian Klauser    schedule 06.03.2009

Здесь есть много хороших ответов, но я действительно не думаю, что они отвечают, ПОЧЕМУ вы на самом деле ХОТИТЕ использовать максимально строгий интерфейс.

Причины не связаны с вашим первоначальным кодированием, они связаны со следующим посещением или рефакторингом кода — или когда это делает кто-то другой.

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

Button x=otherObject.getVisibleThingy();
frame.add(x);

Вы случайно не знаете, что VisibleThingy — это кнопка, она возвращает кнопку, так что тут все круто (приведение не требуется).

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

Поскольку вам НУЖНЫ только методы в Component (родитель как кнопки, так и Toggle, который мог бы быть интерфейсом — то же самое для наших целей), если бы вы написали первую строку следующим образом:

Component x=(Component)otherObject.getVisibleThingy();

Вам не пришлось бы ничего рефакторить - это просто сработало бы.

Это очень простой случай, но он может быть намного сложнее.

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

person Bill K    schedule 06.03.2009

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

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

Вот простой пример:

public interface IText
{
   string Text { get; }
}

public interface ISuperDooper
{
   string WhyAmISuperDooper { get; }
}

public class Control
{
   public int ID { get; set; }
}

public class TextControl : Control, IText
{
   public string Text { get; set; }
}

public class AnotherTextControl : Control, IText
{
   public string Text { get; set; }
}

public class SuperDooperControl : Control, ISuperDooper
{
   public string WhyAmISuperDooper { get; set; }
}

public class TestProgram
{
   static void Main(string[] args)
   {
      List<Control> controls = new List<Control>
               {
                   new TextControl
                       {
                           ID = 1, 
                           Text = "I'm a text control"
                       },
                   new AnotherTextControl
                       {
                           ID = 2, 
                           Text = "I'm another text control"
                       },
                   new SuperDooperControl
                       {
                           ID = 3, 
                           WhyAmISuperDooper = "Just Because"
                       }
               };

       DoSomething(controls);
   }

   static void DoSomething(List<Control> controls)
   {
      foreach(Control control in controls)
      {
         // write out the ID of the control
         Console.WriteLine("ID: {0}", control.ID);

         // if this control is a Text control, get the text value from it.
         if (control is IText)
            Console.WriteLine("Text: {0}", ((IText)control).Text);

         // if this control is a SuperDooperControl control, get why
         if (control is ISuperDooper)
            Console.WriteLine("Text: {0}", 
                ((ISuperDooper)control).WhyAmISuperDooper);
      }
   }
}

запуск этой небольшой программы даст вам следующий результат:

ID: 1

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

ID: 2

Текст: я еще один текстовый элемент управления

ID: 3

Текст: Просто так

Обратите внимание, что мне не нужно было писать какой-либо код в методе DoSomething, который требовал бы от меня каких-либо знаний обо всех объектах, над которыми я работал, как о конкретных типах объектов. Единственное, что я знаю, это то, что я работаю над объектами, которые являются как минимум экземпляром класса Control. Затем я могу использовать интерфейс, чтобы узнать, что еще у них может быть.

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

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

person Dean Poulin    schedule 06.03.2009

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

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

«Документ» не относится к типу «IStorable», поэтому новичкам может показаться непонятным, что он присваивается isDoc. Явным приведением автор (книги и кода) говорит, что документ может быть приведен к объекту IStorable, но это НЕ ТО ЖЕ ТО ЖЕ САМОЕ, что и объект IStorable.

person DevinB    schedule 06.03.2009

Дело в том, что объект (откуда вы его взяли?) может не реализовывать интерфейс, и в этом случае генерируется исключение, которое можно поймать и обработать. Конечно, вы можете использовать оператор «is» для проверки и оператор «as» для приведения вместо приведения в стиле C.

person royatl    schedule 06.03.2009
comment
Может кто-нибудь объяснить, почему вы проголосовали за это? Спасибо. - person royatl; 06.03.2009

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

Дополнительные сведения см. в следующей статье: Интерфейсы

person Tom Deleu    schedule 09.03.2009

Основная причина, по которой вы должны явно приводить к интерфейсу, заключается в том, что члены интерфейса реализованы явно (т.е. с полными именами в форме InterfaceName.InterfaceMemberName). Это связано с тем, что когда вы полностью уточняете их с помощью имени интерфейса, эти члены фактически не являются частью API реализующего класса. Вы можете только получить к ним доступ через приведение к интерфейсу.

Вот пример, который вы можете запустить как есть:

using System;

public interface ISomethingDoer {
    void DoSomething();
}

public class ThingA : ISomethingDoer {

    public void DoSomething(){
        Console.WriteLine("ThingA did it!");
    }
}

public class ThingB : ISomethingDoer {
    
    // This is implemented explicitly by fully-qualifying it with the interface name
    // Note no 'scope' here (e.g. public, etc.)
    void ISomethingDoer.DoSomething(){ 
        Console.WriteLine("ThingB did it!");
    }
}

public static class Runner {

    public static void Main(){

        var a = new ThingA();
        a.DoSomething(); // Prints 'ThingA did it!'

        var b = new ThingB();
        b.DoSomething(); // NOTE: THIS WILL NOT COMPILE!!!

        var bSomethingDoer = (ISomethingDoer)b;
        bSomethingDoer.DoSomething(); // Prints 'ThingB did it!'
    }
}

ХТХ!

person Mark A. Donohoe    schedule 06.11.2020