2009-09-21 124 views
5

我遇到了方法返回类型的问题。如何在方法中返回动态返回类型? C#

该方法返回一个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),并将其转换为相应的类型和retreive属性和林不知道如何重构这个,因为它将在我们正在做的几页上使​​用。

+0

定义'protected abstract void tblAppointment.Process()'并在每个类中覆盖它,然后使用'appointmentRepos.GetAppointment(id).Process()'。 – 2009-10-01 10:39:17

回答

7

你解决错误的问题。如果你有一个超A,与子类BC等,所有具有类似的功能,要做到以下几点:

  1. ABC等实现一个接口。使用BC实例的代码通过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单独从实际BC实现,你不运行的不小心忘记添加相应的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); 

或者,甚至更好的想法可能是把你的BC类成自定义控件,因为他们似乎封装显示逻辑。

+0

但是,如果当前的switch语句在表单上设置标签和输入值,这将如何工作?对不起,我知道这可能是基本的,但.NET花了一点时间去适应比我想象的:) – Andrew 2009-09-23 10:35:20

0

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

好消息是,当您投射引用类型时,您并未创建新对象 - 您只是简单地更改指向您已拥有的对象的引用类型,访问其成员。

+1

这是一个真实的陈述,但它并没有帮助他解决手头上的问题...... – 2009-09-21 16:04:36

1

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

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

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

    return null; 
} 

也就是说,如果你想用的ID实际上不是BirthAppointment虽然使用方法会打破,所以你可能会考虑检查。

var viewSingleBirthAppointment = appointmentRepos.GetBirthAppointment(appointmentId); 
2

你可以创建一个通用的方法:

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

但那么你就需要调用它之前知道对象的实际类型...

7

好吧,如果你正在使用C# 4你可能使用动态输入...但如果你想坚持静态输入,我怀疑你可以做的最好的是提供预期的类型作为泛型类型的参数,并获得执行c AST为您提供:

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,如果他们不知道)。在编译时不知道适当的约会类型,很难看出静态类型如何更有利于你,真的......

+0

var app = GetAppointment (10); 我可以这样做: var viewSingleAppointment = appointmentRepos.GetAppointment(appointmentId); var app = GetSingleAppointment (appointmentId); – Andrew 2009-09-22 09:09:55

+0

@SocialAddict:不,因为必须在编译时知道泛型类型参数。无论如何,它会有什么好处?如果您在编译时不知道约会类型,您将如何知道要使用哪些属性? – 2009-09-22 09:11:09

+0

上述代码的问题是,我不知道预约类型,直到我有从getAppointment返回的typeID。我可以通过在每个中使用类似这样的大选择语句使其工作: switch(viewSingleAppointment.tblAppointmentType.appTypeID) { case 1: {tblSingleBirthAppointment myApp =(tblSingleBirthAppointment)viewSingleAppointment; //获取相关属性并设置为查看 } 就像我最终会在多个页面上使用非常长的select语句并且不确定如何重构? – Andrew 2009-09-22 11:23:20

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() ;