【问题标题】:A way of casting a base type to a derived type一种将基类型转换为派生类型的方法
【发布时间】:2010-09-12 13:35:21
【问题描述】:

我不确定这是否是一件奇怪的事情,或者它是否是某种代码味道......但我想知道是否有办法(某种 oop 模式会很好)将基类型“强制转换”为其派生类型的一种形式。我知道这没有什么意义,因为派生类型将具有父级不提供的附加功能,而这些功能本身并不完全合理。但是有没有办法做到这一点?这是一个代码示例,因此我可以更好地解释我在问什么。

public class SomeBaseClass {
    public string GetBaseClassName {get;set;}
    public bool BooleanEvaluator {get;set;}
}

public class SomeDerivedClass : SomeBaseClass {
    public void Insert(SqlConnection connection) {
          //...random connection stuff
          cmd.Parameters["IsItTrue"].Value = this.BooleanEvalutar;
          //...
    }
}

public static void Main(object[] args) {
    SomeBaseClass baseClass = new SomeBaseClass();
    SomeDerivedClass derClass = (SomeDerivedClass)baseClass; 
    derClass.Insert(new sqlConnection());
}

我知道这看起来很愚蠢,但是有什么办法可以完成这种事情吗?

【问题讨论】:

  • 在这个问题中使用“cast”这个词是错误的。铸造意味着我们铸造的对象已经是这种类型的对象。例如:对象o = 3; MethodWithIntegerParam((int)o);在您的问题中,baseClass 不是用派生类实例化的。所以你要找的是转换而不是投射。

标签: c# oop inheritance design-patterns


【解决方案1】:

在“托管”语言中并不健全。这是向下转换,并且没有理智的方法来处理它,这正是您所描述的原因(子类提供的不仅仅是基类 - 这个“更多”来自哪里?)。如果您确实希望特定层次结构具有类似的行为,则可以使用将基类型作为原型的派生类型的构造函数。

人们可以用反射构建一些东西来处理简单的情况(更具体的类型没有附加状态)。一般来说,只需重新设计即可避免该问题。

编辑:糟糕,不能在基本/派生类型之间编写转换运算符。微软试图“保护你”免受你自己的一种奇怪。啊,好吧,至少他们没有太阳那么糟糕。

【讨论】:

  • 在这种情况下,您不能使用转换/转换运算符。
  • Ah well, at least they're no where near as bad as Sun. - 你是什么意思?只是出于好奇;我不知道Java。 (我知道已经 4 年了。)
  • @KonradMorawski 我指的是 Java 语言功能,旨在确保您编写“好”代码(其中“好”是他们的意见)。我最讨厌被检查的异常(所以你不能永远忽略异常)。
  • “糟糕,不能在基本/派生类型之间编写转换运算符。” .Net 允许这样做,并且可以使用 C# 来实现。见stackoverflow.com/a/15987692/1497385
【解决方案2】:

尝试组合而不是继承!

在我看来,您最好将 SomeBaseClass 的实例传递给 SomeDerivedClass(它将不再派生基类,应该重命名)

public class BooleanHolder{       
    public bool BooleanEvaluator {get;set;}
}

public class DatabaseInserter{
    BooleanHolder holder;

    public DatabaseInserter(BooleanHolder holder){
        this.holder = holder;
    }

    public void Insert(SqlConnection connection) {
          ...random connection stuff
          cmd.Parameters["IsItTrue"].Value = holder.BooleanEvalutar;
          ...
    }
}

public static void Main(object[] args) {
    BooleanHolder h = new BooleanHolder();
    DatabaseInserter derClass = new DatabaseInserter(h);
    derClass.Insert(new sqlConnection);
}

查看http://www.javaworld.com/javaworld/jw-11-1998/jw-11-techniques.html(第 3 页):

通过组合来重用代码 为苹果提供了另一种方式 重用 Fruit 的实现 剥()。而不是扩展水果, 苹果可以持有对水果的引用 实例并定义自己的剥离() 简单地调用剥离()的方法 水果。

【讨论】:

  • 虽然我更喜欢组合而不是继承,但“苹果有果实”而不是“苹果是果实”的想法是我见过的最糟糕的例子。如果 Apple 定义了它自己的 Peel 方法,那么它就不能再替代 Fruit - 这会破坏整个 Fruit 的概念。
  • 与其说“苹果有果实”,不如把它读作“苹果有果实的特性”,这听起来比“苹果是果实”要好得多,实际上也更准确。
  • 我认为“水果的属性”既不是组合也不是继承,而是接口的实现:)
【解决方案3】:

我个人认为在这种情况下使用继承是不值得的。相反,只需在构造函数中传递基类实例并通过成员变量访问它。

private class ExtendedClass //: BaseClass - like to inherit but can't
{
    public readonly BaseClass bc = null;
    public ExtendedClass(BaseClass b)
    {
        this.bc = b;
    }

    public int ExtendedProperty
    {
        get
        {
        }
    }
}

【讨论】:

  • 我发现这非常有用,尤其是在将基类列表转换为每个列表项中具有更多功能的另一个列表时。
  • 但是在这种情况下不可能调用基类方法,因为ExtendedClass根本没有它们
  • 是的,你可以:extendedObject.BaseClass.BaseClassMethod(args)
  • 这看起来像一个几乎是“装饰器”的模式,在这种情况下可以工作并且值得谷歌搜索!设计模式有大部分答案!
【解决方案4】:

如果你有一个派生类的对象,但它被基类类型的引用引用,并且由于某种原因你希望它被派生类类型引用引用,那么向下转换是有意义的。换句话说,您可以向下转换以反转先前向上转换的效果。但是您不能让派生类类型的引用引用基类的对象。

【讨论】:

  • 有趣的是,当您“反转”向上转换时,所有“额外”派生信息都可以再次访问。显然,它以某种方式保留在内存堆中。
  • 我一直在阅读这个问题。将信息保留在该对象上的原因是,当您向上转换对象时,对象本身并没有改变,只是对它的引用。并且重新回答这个答案,这似乎是一个完全合理的沮丧理由。尤其是在处理 SOA 时。
【解决方案5】:

我并不是说我推荐这个。但是你可以把基类转成 JSON 字符串,然后再转成派生类。

SomeDerivedClass layer = JsonConvert.DeserializeObject<SomeDerivedClass>(JsonConvert.SerializeObject(BaseClassObject));

【讨论】:

  • “我不是说我推荐这个。”
【解决方案6】:

不,这是不可能的。在像 C# 这样的托管语言中,它是行不通的。运行时不允许它,即使编译器允许它通过。

你自己说这看起来很愚蠢:

SomeBaseClass class = new SomeBaseClass();
SomeDerivedClass derClass = (SomeDerivedClass)class; 

所以问问自己,class 实际上是 SomeDerivedClass 的一个实例吗?不,所以转换没有意义。如果您需要将SomeBaseClass 转换为SomeDerivedClass,那么您应该提供某种转换,无论是构造函数还是转换方法。

不过,听起来您的类层次结构需要一些工作。一般来说,不应该可以将基类实例转换为派生类实例。通常应该有不适用于基类的数据和/或功能。如果派生类功能适用于基类的所有个实例,那么它应该被汇总到基类中,或者被拉入一个不属于基类层次结构的新类中。 p>

【讨论】:

  • “不,这是不可能的。在像 C# 这样的托管语言中,它是行不通的。” .Net 允许这样做,并且可以使用 C# 来实现。见stackoverflow.com/a/15987692/1497385
【解决方案7】:

C# 语言不允许此类运算符,但您仍然可以编写它们并且它们可以工作:

[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Implicit(Base a) { ... }

[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Explicit(Base a) { ... }

【讨论】:

  • 如果您分配 List = List ,隐式操作将如何工作?
  • @BoppityBop 它的工作方式与List&lt;long&gt; = List&lt;int&gt;List&lt;int&gt; = List&lt;long&gt; 相同——它不起作用。不过,您可以使用一些隐式转换运算符创建自己的集合。
  • 只是为了清楚。你说op_Implicit 根据你的解决方案将不会被调用当基/派生在一个通用集合中时。对吗?
  • @BoppityBop 是的。运算符是静态的。甚至没有运算符继承。更不用说“泛型类元素类型运算符继承”了。
【解决方案8】:

是的 - 这是一种代码味道,并且几乎可以确定您的继承链已损坏的事实。

我的 猜测(来自有限的示例)是您宁愿让 DerivedClass 对 SomeBaseClass 的实例进行操作 - 这样“DerivedClass 有一个 SomeBaseClass”,而不是比“DerivedClass 是一个 SomeBaseClass”。这被称为“偏好组合优于继承”。

【讨论】:

    【解决方案9】:

    正如其他人所指出的,您建议的演员阵容实际上是不可能的。 会不会是可以引入Decorator pattern(Head First extract)的情况?

    【讨论】:

    【解决方案10】:

    您是否考虑过当前您的基类和派生类都将实现的接口?我不知道您为何采用这种方式的具体原因,但它可能会起作用。

    【讨论】:

      【解决方案11】:

      这被称为向下转换,Seldaek 使用“安全”版本的建议是合理的。

      这是一个相当不错的description with code samples

      【讨论】:

        【解决方案12】:

        这是不可能的,因为你将如何获得派生类所拥有的“额外”。当您实例化它时,编译器如何知道您的意思是derivedClass1 而不是derivedClass2?

        我认为您真正要寻找的是工厂模式或类似模式,因此您可以在不知道要实例化的显式类型的情况下实例化对象。在您的示例中,具有“插入”方法将是工厂返回的实例实现的接口。

        【讨论】:

          【解决方案13】:

          我不知道为什么没有人这么说,我可能错过了一些东西,但你可以使用 as 关键字,如果你需要做一个 if 语句,使用 if。

          SomeDerivedClass derClass = class as SomeDerivedClass; //derClass is null if it isnt SomeDerivedClass
          if(class is SomeDerivedClass)
              ;
          

          -edit-I asked this question long ago

          【讨论】:

            【解决方案14】:

            我最近需要使用派生类型扩展一个简单的 DTO,以便在其上添加更多属性。然后我想重用我拥有的一些转换逻辑,从内部数据库类型到 DTO。

            我解决它的方法是在 DTO 类上强制一个空的构造函数,像这样使用它:

            class InternalDbType {
                public string Name { get; set; }
                public DateTime Date { get; set; }
                // Many more properties here...
            }
            
            class SimpleDTO {
                public string Name { get; set; }
                // Many more properties here...
            }
            
            class ComplexDTO : SimpleDTO {
                public string Date { get; set; }
            }
            
            static class InternalDbTypeExtensions {
                public static TDto ToDto<TDto>(this InternalDbType obj) where TDto : SimpleDTO, new() {
                    var dto = new TDto {
                        Name = obj.Name
                    }
                }
            }
            

            然后,在转换为复杂 DTO 时,我可以重用来自简单 DTO 的转换逻辑。当然,我将不得不以其他方式填充复杂类型的属性,但是使用简单 DTO 的许多许多属性,这确实简化了 IMO。

            【讨论】:

              【解决方案15】:

              那是行不通的。去看看编译错误链接的帮助页面。

              最好的解决方案是在这里使用工厂方法。

              【讨论】:

              • 大声笑...我知道我正试图让我的思维过程跨越...相信我我试过了:D
              • "那行不通 :)" .Net 允许这样做,并且可以使用 C# 来实现。见stackoverflow.com/a/15987692/1497385
              【解决方案16】:

              正如许多答案所指出的那样,您不能沮丧,这完全有道理。

              但是,在您的情况下,SomeDerivedClass 没有“缺失”的属性。所以你可以像这样创建一个扩展方法:

              public static T ToDerived<T>(this SomeBaseClass baseClass) 
                  where T:SomeBaseClass, new()
              {
                  return new T()
                  {
                      BooleanEvaluator = baseClass.BooleanEvaluator,
                      GetBaseClassName = baseClass.GetBaseClassName
                  };
              }
              

              所以你不是在投射,只是在转换:

              SomeBaseClass b = new SomeBaseClass();
              SomeDerivedClass c = b.ToDerived<SomeDerivedClass>();
              

              这只有在基类中的所有数据都是可读可写属性的形式时才真正有效。

              【讨论】:

                【解决方案17】:

                C++ 使用构造函数处理它。 C++ Typecasting。这对我来说似乎是一个疏忽。你们中的许多人都提出了该过程将如何处理额外属性的问题。我会回答,当程序员不设置属性时,编译器在创建派生类时会做什么?我已经处理了类似于 C++ 的这种情况。我创建了一个采用基类的构造函数,然后在构造函数中手动设置属性。这绝对比在派生类中设置变量并破坏继承更可取。我也会选择它而不是工厂方法,因为我认为生成的代码看起来会更干净。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2012-12-06
                  • 2022-10-14
                  • 1970-01-01
                  • 2016-11-15
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多