代码之家  ›  专栏  ›  技术社区  ›  Andrew

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

  •  5
  • Andrew  · 技术社区  · 16 年前

    方法的返回类型有问题。

    方法返回一个Linq对象,该对象当前返回类型tblap软膏。该方法如下:

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

    问题是tblappoment是抽象的,并且有许多继承它的子类型。当我尝试返回“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
    
    }
    

    编辑:我已经完成了这项工作,但我需要为每个约会(大约20个)使用一个select语句,并将它们强制转换为适当的类型,然后检索属性,我不确定如何像在我们正在做的几页中那样重构它。

    6 回复  |  直到 16 年前
        1
  •  7
  •   Daniel Pryden    16 年前

    你在解决错误的问题。如果你有超类 A ,带有子类 B , C 等等,如果所有功能都相似,您需要执行以下操作:

    1. 制作 一个接口 , C 等。实施。使用的代码 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 语句。事实上,这在以下几个方面是更好的:

      • 它使用更少的总体代码。
      • 由于案例的实现存在于各种实现类中,因此不需要强制转换;它们可以直接访问所有成员变量。
      • 因为你不是在建造一个大的 转换 / case 分离 从实际出发 C 实现时,不存在意外忘记添加相应的 案例 如果添加新的子类。如果你离开 DoSomething() 方法的子类 A ,您将得到一个编译时错误。

    编辑 :回应您的评论:

    如果你 剂量学() 例行程序需要在 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);
    

    或者,一个更好的办法是 C 类转换为自定义控件,因为它们似乎封装了显示逻辑。

        2
  •  7
  •   Jon Skeet    16 年前

    如果你用的是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 如果他们没有)。在编译时不知道合适的预约类型,很难看出静态类型如何为您提供更多的帮助,真的…

        3
  •  2
  •   Thomas Levesque    16 年前

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

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

    但在调用对象之前,您需要知道它的实际类型…

        4
  •  2
  •   Anton Tykhyy    16 年前

    当你打电话 .GetType() ,您将获得对象的运行时类型。C编译器不知道对象的运行时类型。它只知道对象将是从 tblAppointment 因为您在方法声明中这样说,所以返回值的静态类型是 tblapp软膏 .因此 任务期限 是您所能访问的全部内容,除非您使用强制转换来告诉编译器“我知道在运行时,此引用将引用此类型的对象,请插入运行时检查并提供此静态类型的引用”。

    静态类型化就是在编译时和运行时所知道的类型之间的差异。如果您来自一种动态类型的语言,比如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 () ;
    
        5
  •  1
  •   Pete Michaud    16 年前

    您可以创建另一个方法来封装转换:

    public tblSingleBirthAppointment GetBirthAppointment(int id)
    {
        var singleAppointment = GetAppointment(id);
    
        if (singleAppointment != null)
        {
            return (tblSingleBirthAppointment)singleAppointment;
        }
    
        return null;
    }
    

    如果您试图将它与一个实际上不是分娩预约的ID一起使用,那么这个方法将被破坏,因此您可以考虑检查。

    var viewSingleBirthAppointment = appointmentRepos.GetBirthAppointment(appointmentId);
    
        6
  •  0
  •   Andrew Hare    16 年前

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

    好消息是,在转换引用类型时,您不会创建新的对象-您只需更改指向已具有的对象的引用类型,从而使您可以访问其成员。