Динамическое Кастинг C # - PullRequest
1 голос
/ 30 ноября 2011

У меня есть родительский класс X и родительский класс H.

H имеет в своих полевых данных ссылку на тип X. Для конструктора H требуется экземпляр типа X, который затем сохраняется в ссылке.

Затем я наследую от X дочерний класс x1, а от H дочерний класс h1.

h1 примет в своем конструкторе экземпляр X или x1.

Но методы и свойства x1, которые еще не определены в родительском классе X, будут не доступными для h1.

Как я могу окончательно привести x1 к типу x1 в моем классеh1

Ответы [ 8 ]

8 голосов
/ 30 ноября 2011

Давайте начнем с того, что переписаем вопрос в нечто понятное. Вместо X и H, давайте назовем их Пищей и Животным. Ваш сценарий:

abstract class Animal
{
    protected Food favourite;
    protected Animal(Food f) { this.favourite = f; }
}
abstract class Food
{
}
sealed class Banana : Food 
{
    public void Peel() {}
}
sealed class Monkey : Animal
{
    public Monkey(Banana banana) : base(banana) {}
    public PeelMyBanana()
    {
        this.favourite.Peel(); // Error, favourite is of type Food, not Banana
    }
}

И ваш вопрос:

Как я могу навсегда сделать так, чтобы «любимый» был известен как «Банан» внутри «Обезьяны»?

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

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

sealed class Monkey : Animal
{
    public Monkey(Banana banana) : base(banana) {}

    private Banana Favourite { get { return (Banana)this.favourite; } }        

    public PeelMyBanana()
    {
        this.Favourite.Peel(); // Works
    }
}

Или вы можете обобщить базовый класс:

abstract class Animal<F> where F : Food
{
    protected F favourite;
    protected Animal(F f) { this.favourite = f; }
}
abstract class Food
{
}
sealed class Banana : Food 
{
    public void Peel() {}
}
sealed class Monkey : Animal<Banana>
{
    public Monkey(Banana banana) : base(banana) {}
    public PeelMyBanana()
    {
        this.favourite.Peel(); // Legal; Animal<Banana>.favourite is of type Banana
    }
}
4 голосов
/ 30 ноября 2011

Не существует такой вещи, как «постоянное» приведение (или, скорее, идея «постоянного броска» бессмысленна, поскольку это подразумевает, что приведение делает что-то с объектом , что и делает нет, так что нечего либо упорствовать, либо отменить). Ваша переменная имеет тип H, и тип переменной никогда не изменится. Ваш экземпляр имеет тип H1, и тип экземпляра никогда не изменится. Приведение просто говорит компилятору: «Да, хотя я только ссылаюсь на этот экземпляр как тип H, я на самом деле знаю, что он имеет тип H1, поэтому безопасно хранить его в любой переменной, которая может ссылаться на экземпляр H1 ".

Если ваша структура выглядит следующим образом:

class H
{

}

class X
{
    public H HValue { get; private set; }

    public X(H h)
    {
        HValue = h;
    }
}

class H1 : H
{
    public void Foo() { }
}

class X1 : X
{
    public X1(H1 h1) : base(h1)
    {

    }
}

Тогда вам придется хранить значение для переменной h1 где-то еще, если вы всегда хотите использовать это значение без принижения.

Теперь, немного "вонючий" способ выполнить то, что вы хотите (не нужно каждый раз писать код приведения), вы можете сделать это:

class X1 : X
{
    public X1(H1 h) : base(h) { }

    public new H1 HValue 
    { 
        get { return (H1)base.HValue; }
    }
}

Это позволит вам ссылаться на то же свойство с именем HValue везде, где у вас есть ссылка на X1 в переменной типа X1. Итак ...

X x = new X1(new H1());  // x.HValue would be of type H, even though the 
                         // reference itself is H1
X1 x = new X1(new H1()); // x.HValue would be of type H1
2 голосов
/ 30 ноября 2011

Если я правильно понимаю, у вас есть следующие классы.

class X
{
}

class X1 : X
{
}

class H
{
    H(X x)
    {
        MyX = x;
    }

    X MyX { get; private set; }
}

class H1 : H
{
    H1(X x) : base(x)
    {
    }
}

Вы хотите, чтобы H1 имел дело с MyX, как если бы это был более конкретный тип X1.

Самый простой способ сделать это - иметь другое свойство в H1, которое ссылается на x как X1:

class H1 : H
{
    H1(X1 x) : base(x)
    {
        MyX1 = x;
    }

    X1 MyX1 { get; private set; }
}

Создавая конструктор в H1, который принимает только X1 (а не старый X), вы гарантируете, что он всегда будет установлен правильно.

1 голос
/ 30 ноября 2011

Вот для чего дженерики. Если я правильно понимаю, что вы спрашиваете, ваш класс H должен быть общим для некоторого "C" (класс H ), где C наследуется от X, так что H может быть написано для работы с X, но тогда H1 может быть определяется как производная от H , чтобы он мог работать с X1.

РЕДАКТИРОВАТЬ: Конечно, в зависимости от того, насколько сложны эти классы, может быть не стоит создавать универсальный H. В таком случае я бы согласился с ответом Мэтта Дилларда.

0 голосов
/ 30 ноября 2011

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

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

Мартин Фаулер - Рефакторинг Джошуа Кериевский - Рефакторинг к паттернам Кент Бек - Шаблоны реализации (хороший пример решения этой проблемы путем пересмотра ваших абстракций).

0 голосов
/ 30 ноября 2011

Если вы ориентируетесь на .Net 4.0, вы можете использовать новый динамический тип, описанный здесь . Функционально он позволит вам делать то, что вы хотите, но с точки зрения дизайна он бросает вызов концепции строгой проверки типов во время компиляции.

0 голосов
/ 30 ноября 2011

Вы можете использовать Интерфейс, поэтому H имеет ссылку не на X, а на IX, затем X и x1 реализуют IX метод / свойства.

0 голосов
/ 30 ноября 2011

Не думаю, что ты можешь.

Вы можете добавить поле типа x1 в h1 и установить его, если вам дан x1 в конструкторе h1, а затем использовать это поле для доступа к методам x1 в h1, к которым вы хотите получить доступ (помня, что поле x1 может не быть установлен).

Из вашего описания не ясно, выглядит ли конструктор h1 следующим образом:

h1(X xInstance)

или

h1(X xinstance, x1 x1Instance)

но я предполагаю первое, даже если вы говорите, что «h1 примет в своем конструкторе экземпляр X и x1». в противном случае вы знаете, что у вас есть x1, и поэтому он становится довольно простым:)

так что я думаю, что вы хотите сделать это:

public class H1: H
{
    private X m_x;
    private X1 m_x1;

    public H1(X x):base(x)
    {
        m_x=x;
        X1 x1 = x as X1;
        if (x1!=null)
            m_x1=x1;
    }
}
...