如何在方法中返回动态返回类型? C#

时间:2009-09-21 15:56:28

标签: c# linq-to-sql generics return-type

我遇到了方法的返回类型问题。

该方法返回一个linq对象,该对象目前返回类型tblAppointment。该方法如下所示:

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

}

问题是tblAppointment是抽象的并且有很多继承它的子类型。当我尝试返回类型为“appointmentTypeA”的对象并在其上调用.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

}

编辑:我有这个工作,但我需要为每个约会使用一个select语句(大约20)并将它们转换为适当的类型并检索属性,我不知道如何重构它,因为它将被用于我们正在做几页。

6 个答案:

答案 0 :(得分:7)

好吧,如果您正在使用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)。在编译时不知道适当的约会类型,很难看出静态类型如何能为你带来更多好处,真的...

答案 1 :(得分:7)

你正在解决错误的问题。如果您有一个超类A,子类BC等,它们都具有相似的功能,那么您需要执行以下操作:

  1. 使A BC等实现的界面成为B。适用于CA个实例的代码通过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 } ... 提供的界面完成。如果您可以定义一组适用于所有类型的通用操作,那么您就需要这样做。

  2. 如果您无法定义一组通用操作,例如你有类似的代码:

    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语句的相应分支。这在几个方面实际上更好:

    • 使用较少的整体代码。
    • 由于案例的实现存在于各种实现类中,因此不需要进行转换;他们可以直接访问所有成员变量。
    • 由于您没有从实际的caseB实施中构建一个大的C / case单独,因此您不能如果添加新的子类,则运行任何意外忘记添加相应DoSomething()的风险。如果将A方法保留在DoSomething()的子类之外,则会出现编译时错误。
  3. 修改:回复您的评论:

    如果您的Form例程需要在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); 或其他GUI元素上运行,只需将该元素传递给方法即可。例如:

    B

    或者,更好的想法可能是将C和{{1}}类转换为自定义控件,因为它们似乎封装了显示逻辑。

答案 2 :(得分:2)

您可以创建一个通用方法:

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

但是在调用它之前你需要知道对象的实际类型......

答案 3 :(得分:2)

当您调用.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 () ;

答案 4 :(得分:1)

您可以创建另一种方法来封装演员:

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

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

    return null;
}

如果您尝试将其用于实际上不是BirthAppointment的ID,那么该方法会破坏,因此您可以考虑进行检查。

var viewSingleBirthAppointment = appointmentRepos.GetBirthAppointment(appointmentId);

答案 5 :(得分:0)

如果要返回对父类型的子类型的引用,则引用将属于该类型,并且编译器将不允许您访问任何子类型的成员,直到转换为该类型。这是行动中的多态:)

好消息是,在构建引用类型时,您不是在创建新对象 - 您只需更改指向您已有对象的引用类型,从而为您提供对其成员的访问权限。