C# понижающее преобразование при привязке к интерфейсу

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

static void Main(string[] args) {
    List<Animal> list = new List<Animal>();  
    Pig p = new Pig(5);  
    Dog d = new Dog("/images/dog1.jpg");  
    list.Add(p);  
    list.Add(d);  
    foreach (Animal a in list)   
    {  
        DoPigStuff(a as Pig);  
        DoDogStuff(a as Dog);  
    }  

}  


static void DoPigStuff(Pig p)
{
    if (p != null) 
    {  
        label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
    }  
}

static void DoDogStuff(Dog d) {
    if (d != null) 
    {
        Image1.src = d.Image;
    }
}

class Animal {
    public String Name { get; set; }
}

class Pig : Animal{
    public int TailLength { get; set; }

    public Pig(int tailLength) 
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }
}

class Dog : Animal {
    public String Image { get; set; }

    public Dog(String image) 
    {
        Name = "Mr Dog";
        Image = image;
    }
}

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

Jonathan Rupp 29.09.2008 06:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
1
2 814
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Почему бы не включить в Animal объект абстрактный метод, который Pig и Dog вынуждены реализовать?

public class Animal
{
    public abstract void DoStuff();
}

public Dog : Animal
{
    public override void DoStuff()
    {
        // Do dog specific stuff here
    }
}

public Pig : Animal
{
    public override void DoStuff()
    {
        // Do pig specific stuff here
    }
}

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

Спасибо за ответ, хотя это идеальный способ разработки класса, если DoStuff работает только в рамках класса. Я спрашиваю о привязке к пользовательскому интерфейсу.

Corin Blaikie 29.09.2008 05:46

Вы не в полной мере используете свой базовый класс. Если бы у вас была виртуальная функция в вашем классе Animal, которую переопределяла Dog & Pig, вам не нужно было бы ничего приводить.

Если у вас нет более конкретного примера, просто переопределите ToString ().

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

Corin Blaikie 29.09.2008 05:48

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

if (animal is Pig) DoPigStuff();
if (animal is Dog) DoDogStuff();

То, что вы ищете, - это множественная отправка. НЕТ - C# не поддерживает множественную отправку. Он поддерживает только однократную отправку. C# может только динамически вызывать метод в зависимости от типа получателя (т. Е. Объекта в левой части. В вызове метода)

В этом коде используется двойная отправка. Пусть код говорит сам за себя:

class DoubleDispatchSample
{
    static void Main(string[]args)
    {
        List<Animal> list = new List<Animal>();
        Pig p = new Pig(5);
        Dog d = new Dog(@"/images/dog1.jpg");
        list.Add(p);
        list.Add(d);

        Binder binder = new Binder(); // the class that knows how databinding works

        foreach (Animal a in list)
        {
            a.BindoTo(binder); // initiate the binding
        }
    }
}

class Binder
{
    public void DoPigStuff(Pig p)
    {
        label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
    }

    public void DoDogStuff(Dog d)
    {
        Image1.src = d.Image;
    }
}

internal abstract class Animal
{
    public String Name
    {
        get;
        set;
    }

    protected abstract void BindTo(Binder binder);
}

internal class Pig : Animal
{
    public int TailLength
    {
        get;
        set;
    }

    public Pig(int tailLength)
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }

    protected override void BindTo(Binder binder)
    {
        // Pig knows that it's a pig - so call the appropriate method.
        binder.DoPigStuff(this);
    }
}

internal class Dog : Animal
{
    public String Image
    {
        get;
        set;
    }

    public Dog(String image)
    {
        Name = "Mr Dog";
        Image = image;
    }

    protected override void BindTo(Binder binder)
    {
        // Pig knows that it's a pig - so call the appropriate method.
        binder.DoDogStuff(this);
    }
}

ПРИМЕЧАНИЕ. Ваш пример кода намного проще, чем этот. Я считаю двойную диспетчеризацию одной из тяжелых артиллерий в программировании на C# - я использую ее только в крайнем случае. Но если есть много типов объектов и много разных типов привязок, которые вам нужно сделать (например, вам нужно привязать его к HTML-странице, но вам также необходимо привязать его к WinForms, отчету или CSV) , Я бы в конечном итоге реорганизовал свой код, чтобы использовать двойную отправку.

Ответ принят как подходящий

Столкнувшись с проблемой такого типа, я следую шаблон посетителя.

interface IVisitor
{
  void DoPigStuff(Piggy p);
  void DoDogStuff(Doggy d);
}

class GuiVisitor : IVisitor
{
  void DoPigStuff(Piggy p)
  {
    label1.Text = String.Format("The pigs tail is {0}", p.TailLength);
  }

  void DoDogStuff(Doggy d)
  {
    Image1.src = d.Image;
  }
}

abstract class Animal
{
    public String Name { get; set; }
    public abstract void Visit(IVisitor visitor);
}

class Piggy : Animal
{
    public int TailLength { get; set; }

    public Piggy(int tailLength) 
    {
        Name = "Mr Pig";
        TailLength = tailLength;
    }

    public void Visit(IVisitor visitor)
    {
       visitor.DoPigStuff(this);
    }
}

class Doggy : Animal 
{
   public String Image { get; set; }

   public Doggy(String image) 
   {
     Name = "Mr Dog";
     Image = image;
   }

   public void Visit(IVisitor visitor)
   {
     visitor.DoDogStuff(this);
   }
}

public class AnimalProgram
{
  static void Main(string[] args) {
    List<Animal> list = new List<Animal>();  
    Pig p = new Pig(5);  
    Dog d = new Dog("/images/dog1.jpg");  
    list.Add(p);  
    list.Add(d);

    IVisitor visitor = new GuiVisitor();  
    foreach (Animal a in list)   
    {
      a.Visit(visitor);
    }  
  }
}

Таким образом, шаблон посетителя имитирует двойную отправку в обычном объектно-ориентированном языке с единственной отправкой, таком как Java, Smalltalk, C# и C++.

Единственное преимущество этого кода перед Jop состоит в том, что интерфейс IVisitor может быть реализован в другом классе позже, когда вам понадобится добавить новый тип посетителя (например, XmlSerializeVisitor или FeedAnimalVisitor).

Я думаю, вам нужен класс представления, связанный с фабрикой.

Dictionary<Func<Animal, bool>, Func<Animal, AnimalView>> factories;
factories.Add(item => item is Dog, item => new DogView(item as Dog));
factories.Add(item => item is Pig, item => new PigView(item as Pig));

Тогда ваши DogView и PigView унаследуют AnimalView, который выглядит примерно так:

class AnimalView {
  abstract void DoStuff();
}

В итоге вы сделаете что-то вроде:

foreach (animal in list)
  foreach (entry in factories)
    if (entry.Key(animal)) entry.Value(animal).DoStuff();

Думаю, вы также можете сказать, что это реализация шаблона стратегии.

У этой идеи есть обратная сторона: она плохо справляется с уровнями наследования. При добавлении класса BaXuyen: Pig фабрики могут выполнять итерацию в следующем порядке: [Pig, Dog, BaXuyen]. BaXuyen по ошибке отправил бы простой старый PigView вместо BaXuyenView. Используйте вместо этого лучший заказ или посетителя.

Jacob Krall 30.09.2008 02:54

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

Hallgrim 03.10.2008 22:54

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