Есть ли лучший способ привязать список базового класса к пользовательскому интерфейсу, кроме понижающего преобразования, например:
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;
}
}





Почему бы не включить в 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 работает только в рамках класса. Я спрашиваю о привязке к пользовательскому интерфейсу.
Вы не в полной мере используете свой базовый класс. Если бы у вас была виртуальная функция в вашем классе Animal, которую переопределяла Dog & Pig, вам не нужно было бы ничего приводить.
Если у вас нет более конкретного примера, просто переопределите ToString ().
Хммм, да, наличие обоих свойств, используемых в строке, является проблемой с моим примером ... это не совсем то, что мне нужно
Другой способ сделать это - выполнить проверку типов перед вызовом метода:
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. Используйте вместо этого лучший заказ или посетителя.
Хорошая точка зрения! Я бы пошел с заказом, поскольку одним из ограничений было не добавлять код пользовательского интерфейса в модель предметной области.
По сути, вы, кажется, спрашиваете, есть ли способ предоставить различную информацию о привязке для каждого типа элемента в списке. Кроме того, что ты делаешь, нет.