Как вернуть динамические возвращаемые типы в методы? C # - PullRequest
5 голосов
/ 21 сентября 2009

У меня проблема с типом возврата метода.

Метод возвращает объект linq, который в настоящее время возвращает тип tblAppointment. Этот метод показан ниже:

public tblAppointment GetAppointment(int id)
{
    var singleAppointment = (from a in dc.tblAppointments
                                                    where a.appID == id
                                                    select a).SingleOrDefault();
    return singleAppointment;

}

Проблема в том, что tblAppointment является абстрактным и имеет много подтипов, которые его наследуют. Когда я пытаюсь вернуть объект с типом "assignTypeA" и вызываю для него метод .GetType (), он дает мне правильный подтип, но когда я пытаюсь получить доступ к свойствам, он дает мне доступ только к родительским свойствам. , Если я возьму объект и приведу его к новому объекту подтипа, он сработает и позволит мне получить доступ ко всему, что мне нужно, но кажется грязным.

var viewSingleAppointment = appointmentRepos.GetAppointment(appointmentId);

Debug.Write(viewSingleAppointment.GetType()); //returns type i want

if (viewSingleAppointment is tblSingleBirthAppointment)
{
    tblSingleBirthAppointment myApp = (tblSingleBirthAppointment)viewSingleAppointment; //need to do this to access TypeA properties for some reason

}

Редактировать: у меня это работает, но мне нужно использовать оператор выбора для каждой встречи (около 20) и привести их к соответствующему типу и получить свойства, и я не уверен, как это изменить, так как он будет использоваться на несколько страниц мы делаем.

Ответы [ 6 ]

7 голосов
/ 23 сентября 2009

Вы решаете не ту проблему. Если у вас есть суперкласс A, с подклассами B, C и т. Д., Которые имеют схожую функциональность, вы должны сделать следующее:

  1. Сделать A интерфейсом, который реализуют B, C и т. Д. Код, который работает с экземплярами B или C, работает через интерфейс, предоставляемый A. Если вы можете определить общий набор операций, которые работают со всеми типами, то это все, что вам нужно сделать.

  2. Если вы не можете определить общий набор операций, например, у вас есть код, похожий на:

    A foo = GetA();
    if(foo is B) {
        B bFoo = (B) foo;
        // Do something with foo as a B
    } else if(foo is C) {
        C cFoo = (C) foo;
        // Do something with foo as a C
    } ...
    

    Или даже это (что по сути то же самое, просто использование дополнительной информации для эмуляции того, что система типов уже предоставляет вам):

    A foo = GetA();
    MyEnum enumeratedValue = foo.GetEnumeratedValue();
    switch(enumeratedValue) {
        case MyEnum.B:
            B bFoo = (B) foo;
            // Do something with foo as a B
            break;
        case MyEnum.C:
            C cFoo = (C) foo;
            // Do something with foo as a C
            break;
    }
    

    Тогда вы действительно хотите сделать что-то вроде:

    A foo = GetA();
    foo.DoSomething();
    

    Где каждый подкласс будет реализовывать соответствующую ветвь оператора switch. Это на самом деле лучше в нескольких отношениях:

    • Используется меньше общего кода.
    • Поскольку реализации кейсов живут в различных классах реализации, приведение не требуется; они могут обращаться ко всем переменным-членам напрямую.
    • Поскольку вы не строите большой switch / case блок отдельно от реальных реализаций B и C, вы не рискуете случайно забыть добавить соответствующий case, если добавить новый подкласс. Если вы оставите метод DoSomething() вне подкласса A, вы получите ошибку времени компиляции.

Редактировать : В ответ на ваш комментарий:

Если ваша подпрограмма DoSomething() должна работать с Form или другим элементом GUI, просто передайте этот элемент в метод. Например:

public class B : A {
    public void DoSomething(MyForm form) {
        form.MyLabel.Text = "I'm a B object!";
    }
}

public class C : A {
    public void DoSomething(MyForm form) {
        form.MyLabel.Text = "I'm a C object!";
    }
}

// elsewhere, in a method of MyForm:

A foo = GetA();
foo.DoSomething(this);

В качестве альтернативы, еще лучшей идеей может быть превращение ваших классов B и C в пользовательские элементы управления, поскольку они, кажется, инкапсулируют логику отображения.

7 голосов
/ 21 сентября 2009

Что ж, если вы используете C # 4, вы могли бы использовать динамическую типизацию ... но если вы хотите придерживаться статической типизации, я подозреваю, что лучшее, что вы можете сделать, это предоставить ожидаемый введите в качестве аргумента универсального типа и получите метод для выполнения приведения:

public T GetAppointment<T>(int id) where T : tblAppointment
{
    var singleAppointment = (from a in dc.tblAppointments
                                                    where a.appID == id
                                                    select a).SingleOrDefault();
    return (T) singleAppointment;

}

Позвоните по этому номеру:

SpecificAppointment app = GetAppointment<SpecificAppointment>(10);

или используйте неявную типизацию:

var app = GetAppointment<SpecificAppointment>(10);

Он выдаст исключение во время выполнения, если произойдет сбой.

Предполагается, что вызывающий абонент знает тип встречи (хотя он может указать tblAppointment, если не знает). Не зная соответствующего типа встречи во время компиляции, трудно понять, как статическая типизация может принести вам больше пользы, действительно ...

2 голосов
/ 21 сентября 2009

Когда вы вызываете .GetType(), вы получаете тип времени выполнения объекта. Компилятор C # не знает, какой тип времени выполнения будет у вашего объекта. Он только знает, что ваш объект будет иметь тип, производный от tblAppointment, потому что вы так сказали в объявлении метода, поэтому статический тип возвращаемого значения - tblAppointment. Поэтому tblAppointment - это все, к чему вы можете получить доступ, если только вы не используете приведение, чтобы сообщить компилятору «Я знаю, что во время выполнения эта ссылка будет ссылаться на объект этого типа, вставьте проверку во время выполнения и дайте мне ссылку с этим статическим тип ».

Статическая типизация - это разница между типами, известными во время компиляции и во время выполнения. Если вы пришли из динамически типизированного языка, такого как Smalltalk или Javascript, вам придется внести немало корректировок в свои привычки программирования и мыслительные процессы. Например, если вам нужно что-то сделать с объектом, который зависит от его типа во время выполнения, решение часто состоит в том, чтобы использовать виртуальные функции - они распределяются по типу времени выполнения объекта.

Обновление: в вашем конкретном случае используйте виртуальные функции, это именно то, для чего они были созданы:

class tblAppointment
{
    protected abstract void ProcessAppointment () ;
}

sealed class tblBirthAppointment
{
    protected override void ProcessAppointment ()
    {
        // `this` is guaranteed to be tblBirthAppointment
        // do whatever you need
    }
}

...

Тогда используйте

// will dispatch on runtime type
appointmentsRepo.GetAppointment (id).ProcessAppointment () ;
2 голосов
/ 21 сентября 2009

Вы можете создать универсальный метод:

public T GetAppointment<T>(int id) where T : tblAppointment 
{
    var singleAppointment = dc.tblAppointments.SingleOrDefault(a => a.appID == id);
    return (T)singleAppointment;
}

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

1 голос
/ 21 сентября 2009

Вы можете создать другой метод для инкапсуляции приведения:

public tblSingleBirthAppointment GetBirthAppointment(int id)
{
    var singleAppointment = GetAppointment(id);

    if (singleAppointment != null)
    {
        return (tblSingleBirthAppointment)singleAppointment;
    }

    return null;
}

Этот метод сломался бы, если бы вы попытались использовать его с идентификатором, который на самом деле не был BirthAppointment, поэтому вы можете рассмотреть возможность проверки.

var viewSingleBirthAppointment = appointmentRepos.GetBirthAppointment(appointmentId);
0 голосов
/ 21 сентября 2009

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

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

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