【问题标题】:Would .NET be able to function just as well without the use of type Object?如果不使用 Object 类型,.NET 是否也能正常工作?
【发布时间】:2011-04-09 21:51:36
【问题描述】:

我问这个是因为使用 Object 似乎是解决某些问题的一种简单方法,例如“我没有特定类型,所以使用 Object”等。

这让我感到好奇的原因还在于,我的一位同事告诉我,如果 .NET 是一个真正的面向对象平台,那么它就不必像 Object 这样的 catch all 类型。

如果 .NET 没有 Object 类型,那么解决出现的问题以使其功能相同的替代方法是什么?

还需要注意的是,这并不是要抨击 .NET,因为我每天都在工作中使用它。只是想了解更多。

编辑:我记得的另一个注意事项是因为 Object 类型存在,它的效果会在整个 .NET 中产生涟漪。像 IEnumerable 一样存在,但也存在 IEnumerable。在许多情况下,您必须同时实现事物的通用和非通用版本等。

【问题讨论】:

  • 不是必须有一个基础对象才能最终派生出所有东西吗?
  • @ChrisF:不一定; Object 来自什么?
  • @Joan:接口可以说不是从对象“派生”出来的。一方面,接口只派生自其他接口;另一方面,所有接口类型都可以转换为对象,并且所有接口类型都可以在对象上使用方法(ToString 等),因此区别可能并不是真正有区别的。指针类型不是从对象派生的。类型参数类型不是从对象派生的;在运行时,它们将始终是从对象派生的类型,但在编译时与对象没有派生关系。
  • 问十几个不同的程序员什么是“真正的面向对象”,你会得到十几个不同的答案。语言必须具有(或不具有!)才能成为“OO”的功能没有商定的列表。任何试图将一种语言或类型系统归类为“真正的 OO”或不是“真正的 OO”都需要从仔细定义“真正的 OO”的含义开始;否则,每个人都只是互相交谈。
  • @Joan:我对制作任何“更多 OO”的东西都没有一点兴趣。我有兴趣使 C# 更有用 更多任务,以便更多程序员可以用完成更多工作 i>省力。如果 OO 编程的想法朝着这个方向发展,那就太好了。如果函数式编程或声明式编程的想法朝着这个方向发展,我们也会考虑使用它们。我不是,也从来不是“OOP 党派”——OOP 本身并不是善良的;它只有在降低解决实际问题的成本的情况下才有用。这是我的实际目标。

标签: c# .net types base-class-library type-systems


【解决方案1】:

在强类型框架中,对象必须从某个地方开始。

object 类型不是为了提供“简单”的包罗万象的转换选项,或者强迫您降低到最低公分母。它可以为对象提供绝对最一般的情况——参考比较的基础和方便的ToString() 方法等等。

【讨论】:

  • 还有GetType()方法,它继承自System.Object,因为我们知道所有的Objects都有一个Type。
  • 谢谢,你的意思是 Object 有帮助,因为它可以为所有事情提供 ToString 方法?编译器不能在幕后做这件事,这样看起来每种类型都有吗?
  • @Joan:我喜欢 System.Object 明确实现基础知识,包括 GetType()(如 FacticiusVir 提到的)和 ToString()。我不希望编译器在没有反射器的情况下无法看到一些神秘的巫术。
  • 为什么对象必须从某个地方开始?在 C++ 中,他们不必这样做。
  • 似乎你自相矛盾:“不是为了让事情变得简单”和“它是为了提供 [...] 和一个方便的 ToString() 方法”
【解决方案2】:

我想说Object解决的问题不是“我没有特定的类型,所以使用Object”,而是“我真的不在乎这是什么类型;我只需要要知道它是Object"

【讨论】:

    【解决方案3】:

    它很方便,不仅可以用作“通用”类型,还可以用于反射、垃圾收集等。

    其他语言 (C++) 可以不用,但我会犹豫说这使这些语言更 OOP。

    【讨论】:

    • 好吧,我想说在 C++ 中有一个等价于 Object 的:void*
    • @Willy:不,没有人继承自void*。不同(指针)问题的不同解决方案。
    • OP 的问题没有具体提到继承,这个答案也没有。也许我不应该说“等效”,但我认为在许多情况下,C++ void* 概念解决了与 .NET Object 数据类型相同的问题。如果我想在 C++ 中构建某种容器类,但我不知道将什么类型的对象放入我的容器类中,我可以使用void*。或者,我也可以使用 C++ 模板,但 .NET 的唯一等价物是泛型,它仅在 2.0 版中引入。
    【解决方案4】:

    是的,对象类型可能会被滥用,但它为 .NET 世界提供了开创性的功能(首先,IMO 是 GetType())。因此,如果有问题,则与对象的类型无关。

    替代方案很多,包括泛型和 OOP 实践,例如接口...

    【讨论】:

      【解决方案5】:

      请记住,继承表示“is-a”关系。 .NET 中的每个类“都是一个(n)”对象。他们都有一个ToString 方法。它们都有一个类型,您可以通过GetType 访问它。这种关系以及由此产生的功能共享是面向对象编程的基础。

      【讨论】:

      • 谢谢,你的意思是 Object 有帮助,因为它可以为所有事情提供 ToString 方法?编译器不能在幕后做这件事,这样看起来每种类型都有吗?
      • @Joan-Venge 这不是Object 的重点,但我指出所有类都应该并且应该彼此共享一小组属性。这些共性通过Object 类型表示。 'Object' 的许多早期使用现在可能由泛型处理,但这仍然不违背 OO。如果您的所有类都是单个基类的更具体版本,您不应该实现该基类以整合功能并为其他代码片段提供合同吗?
      【解决方案6】:

      如果对象派生层次结构没有一个统一的头部,那么如果不借助动态类型,就不可能测试任意两个任意...呃...事物之间的相等性。

      除此之外,我怀疑对象的功能也可以通过单独的接口(IEqualable 和 IConvertableToString)来处理。另一方面,对象的虚方法有时也很方便,尤其是 ToString,它可以被 IDE 或调试器在显示程序状态时使用。这真是一个务实的设计。

      【讨论】:

      • 谢谢,我也在考虑 IEqualable 和 IConvertableToString。
      【解决方案7】:

      您的朋友可能使用动态语言(如 ruby​​ 或 python),对吧?

      有很多人认为强类型语言应该被称为“面向类”而不是“面向对象”,因为所有可能的行为和多态性都需要在类定义中预先完成。当您的方法接受任何内容时,任何检查都是基于对象功能而不是其类完成的,您可以说这是一种更加面向对象的方法。

      我对这个论点有点矛盾。编程界有一种愚蠢的信念,即无论问题或要求如何,OO 都是明确的好。正因为如此,人们倾向于通过说“某某不是面向对象”来赢得争论,并且由于面向对象是好的同义词,他们赢了。尽管我认为静态语言使用起来很痛苦,但我认为将它们称为不是 OO 是一种不诚实的方式来表达你的观点。另一方面,任何能让程序员走出他的舒适区并学习一种新的做某事的方法(如果没有其他原因,然后赢得争论)的事情都不会是完全坏的。正如我所说,矛盾。 :)

      【讨论】:

      • 他实际上是一名 C++ 程序员,但我认为他对 python 有偏见/好感。
      • 是的,这可能就是他从中得到的。因为他认为 C# 是面向类而不是面向对象的。
      【解决方案8】:

      我对这个主题并不是特别了解,但从我的角度来看,允许人们构建多态组件而无需事先了解这些组件的使用方式是很有用的。我的意思是什么?让我试着解释一下。

      让我们以 .NET 框架的 ArrayList 类为例。在引入泛型之前,这是原始框架的一部分。 ArrayList 类的作者试图提供一个有用的动态列表实现,但他们无法知道将哪些类型的对象插入到列表中。他们使用 Object 类型来表示列表中的项目,因为它允许将任何类型的类添加到列表中。例如:

              ArrayList people = new ArrayList();
              people.Add(new Doctor());
              people.Add(new Lawyer());
              people.Add(new PetDetective());
              people.Add(new Ferrari()); // Yikes!
      
              // ...
      
              for (int i = 0; i < people.Count; i++)
              {
                  object person = people[0];
                  // ...
              }
      

      现在,如果这是您自己的应用程序,并且您知道您的 DoctorLawyerPetDetective 类都派生自一个通用的 Person 基类,那么理论上您可以构建自己的基于Person 类而不是Object 类的链表实现。但是,当您已经构建并测试了 ArrayList 类时,这会带来很多额外的工作,但收益却很小。如果你真的想让它特定于你的Person 基类,那么你总是可以为ArrayList 创建一个只接受Person 派生对象的包装类。

      在 C++ 中,您可以使用“void 指针”数据类型 (void*) 执行基本相同的操作。但是,C++ 也支持模板(与泛型非常相似),这使得构建有用的组件变得更加容易,而无需知道它将与哪些其他类一起使用的细节。由于 C# 最初并不支持泛型,因此使用 Object 类型确实是构建通用多态组件供其他人使用的唯一方法。

      【讨论】:

      • 所以必须添加 Object 类型的原因仅仅是因为 Generics 不在版本 1 中?如果是,我们就不需要 Object 类型?
      • 我真的不知道这是否是驾驶原因;我当然无法说出当时微软的动机是什么。不过,我只是想说明它存在的可能原因。正如其他人指出的那样,它还为 Equals、GetHashCode 和 ToString 提供了一个基本实现,但我同意你的观点,即另一种方法可能是拥有一个定义这些方法的特殊接口并隐式地让编译器使所有类都实现该接口。
      【解决方案9】:

      System.Object 的概念可以被接口取代,通常,所有类都实现了在这个问题上多次提到的想法。虽然我认为这个想法是有效的,但最后我会说它不会给你带来任何东西。即使存在这样的接口,编译器如何确保类实现这些接口的问题仍然存在。

      下面是一些示例代码,我在其中尝试探索没有 System.Object 类型的假设情况,以及底层实现以及这些接口的使用情况。

      // let's start off by defining interfaces to describe the various methods that are currently available from the System.Object class
      
      public interface IEquatable
      {
          bool Equals(IEquatable other);
      }
      
      public interface IHashCodeGenerator
      {
          int GetHashCode();
      }
      
      public interface ITypeIdentifiable
      {
          Type GetType();
      }
      
      public interface IConvertibleToString
      {
          string ToString();
      }
      
      // This guy throws a wrench into things, because we can't privately (or "protectedly") implement an interface.
      // This is discussed further below on the MyClass.MemberwiseClone method.
      public interface IMemberwiseCloneable
      {
      }
      
      // This class simply encapsulates similar functionality found within the System.Object class
      public static class ClrInternals
      {
          [MethodImpl(MethodImplOptions.InternalCall)]
          internal static extern bool Equals(IEquatable objA, IEquatable objB);
      
          [MethodImpl(MethodImplOptions.InternalCall)]
          internal static extern int GetHashCode(IHashCodeGenerator hashGenerator);
      
          [MethodImpl(MethodImplOptions.InternalCall)]
          internal static extern Type GetType(ITypeIdentifiable typedInstance);
      
          [MethodImpl(MethodImplOptions.InternalCall)]
          internal static extern IMemberwiseCloneable MemberwiseClone(IMemberwiseCloneable original);
      }
      
      // let's say that as a rule the compiler implicitly makes all classes implement these interfaces
      class MyClassExampleA : IEquatable, IHashCodeGenerator, ITypeIdentifiable, IConvertibleToString, IMemberwiseCloneable
      {
          // The compiler also implicitly makes all classes implement the interfaces with the following code (unless otherwise specified)
      
          #region IEquatable Members
      
          public bool Equals(IEquatable other)
          {
              // let's suppose that this is equivalent to the current implementation of Object.Equals
              return ClrInternals.Equals(this, other);
          }
      
          #endregion
      
          #region IHashCodeGenerator Members
      
          public int GetHashCode()
          {
              // let's suppose that this is equivalent to the current implementation of Object.GetHashCode
              return ClrInternals.GetHashCode(this);
          }
      
          #endregion
      
          #region ITypeIdentifiable Members
      
          public Type GetType()
          {
              // let's suppose that this is equivalent to the current implementation of Object.GetType
              return ClrInternals.GetType(this);
          }
      
          #endregion
      
          #region IConvertibleToString Members
      
          public string ToString()
          {
              // let's suppose that this is equivalent to the current implementation of Object.ToString
              return this.GetType().ToString();
          }
      
          #endregion
      
          // this one is perhaps a little goofy, since it doesn't satisfy any interface
          // In order to be equivalent to the current Object.MemberwiseClone implementation, I've made this protected,
          // but we cannot have a protected method that implements an interface, so this throws a wrench into things.
          protected MyClassExampleA MemberwiseClone()
          {
              // let's suppose that this is equivalent ot the current implementation of Object.MemberwiseClone
              return (MyClassExampleA)ClrInternals.MemberwiseClone(this);
          }
      
          // ** All of the above code is just a representation of the implicit semantics that the compiler/CLR applies to a class.  Perhaps this code is not actually generated by the compiler for each class (that would be a lot of duplication!), but rather the CLR might handle this logic internally
      }
      
      
      // Ok, so now I'm implementing a general Stack class
      public class Stack
      {
          // what type should I use for the parameter?
          // I have five different interfaces to choose from that I know all classes implement, but which one should I pick?
          public void Push(type??? item)
          {
              // ...
          }
      
          // what type should I use for the return type?
          // I have five interfaces to choose from, but if I return one,
          // then my caller can't utilize the methods defined in the other interfaces without casting.
          // I know all classes implement all five interfaces, but is it possible that my Stack might also contain non-class objects that don't implement all interfaces?  In that case it might be dangerous for the caller to cast the return value from one interface to another.
          public type??? Pop()
          {
              // ...
          }
      
          // In C++ I could have used void* or defined the Stack class as a template
      }
      
      // moving on...
      class StackUtilizer
      {
          // here I try to utilize the Stack class
          public void UseStack(Stack stack)
          {
              // what type should I use for the variable to hold the result of the Stack.Pop method?
              type??? item = stack.Pop();
      
              // if I use IEquatable
              IEquatable item1 = stack.Pop();
      
              IEquatable item2 = stack.Pop();
      
              item1.Equals(item2); // then I can do this
      
              Type itemType = item1.GetType(); // but I can't do this
      
              string s = item1.ToString(); // nor can I do this
      
              // Ok, this calls for another interface that composes all of these other interfaces into one
          }
      }
      
      
      // let's define a single interface that pulls all of these other interfaces together
      public interface IObject : IEquatable, IHashCodeGenerator, ITypeIdentifiable, IConvertibleToString, IMemberwiseCloneable
      {
          // no need to define any methods on this interface.  The purpose of this interface is merely to consolidate all of these other basic interfaces together.
      }
      
      // now we change the compiler rule to say that all classes implicitly implement the IObject interface
      class MyClassExampleB : IObject
      {
          // ... <refer to MyClassExampleA for the implicit implementation of the interfaces>
      }
      
      // now let's try implementing that Stack class again
      public class Stack
      {
          // I know that all classes implement the IObject interface, so it is an acceptable type to use as a parameter
          public void Push(IObject item)
          {
              // ...
          }
      
          // again, since all classes implement IObject, I can use it as the return type
          public IObject Pop()
          {
              // ...
              throw new NotImplementedException("This is an example.  The implementation of this method is irrelevant.");
          }
      }
      
      class StackUtilizer
      {
          // here I try to utilize the Stack class
          public void UseStack(Stack stack)
          {
              // now I can just use IObject for my variables holding the return value of the Stack.Pop method
              IObject item = stack.Pop();
      
              // if I use IObject
              IObject item1 = stack.Pop();
      
              IObject item2 = stack.Pop();
      
              item1.Equals(item2); // then I can do this
      
              Type itemType = item1.GetType(); // and I can do this
      
              string s = item1.ToString(); // and I can do this
          }
      }
      

      所以,最后我们还是有一个 IObject 接口,类似于现在的 System.Object 类。悬而未决的问题是编译器/CLR 将如何处理强制执行我们的规则,即所有类都实现 IObject 接口。 我可以想到三种可能的方法:

      1. 编译器为每个类生成隐式接口实现,会造成大量重复。
      2. CLR 将以一种特殊的方式处理这些接口实现,不需要编译器为每个类实际生成代码。
      3. 我们定义了一个基类,我们称它为Object(听起来很熟悉?),它实现了IObject 接口,我们将规则更改为所有类都隐式继承自Object(这正是我们今天所拥有的,但没有接口)。

      【讨论】:

      • class Stack { public void Push(IStackable item) {...}...} interface IStackable {} class IntStackItem : IStackable { public int value; } // Ta-da! 是的,这很糟糕。没关系。
      • 哈哈。实际上,考虑到这种情况,这是一个非常聪明的方法。谢谢!
      • 再想一想,这将是允许消费者声明一个类/结构/等的一个很好的理由。实现一个接口,即使他们无法控制声明。我的意思是,目前你必须控制一个类的声明才能说它实现了一个接口。能够说一个类实现了一个接口(如果它满足该接口的要求)可能很有用,即使该类的原始作者不知道该接口。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-13
      • 2017-11-30
      • 1970-01-01
      • 2016-08-11
      • 2019-12-25
      • 1970-01-01
      • 2021-01-22
      相关资源
      最近更新 更多