为什么C#中的原始类型有自己的操作?

21
几天前,我决定开始学习C#。于是我买了一本书,开始阅读并练习编码。当我看到C#中的string被认为是原始类型时,我感到惊讶。
但我更惊讶的是,我发现在C#中,string以及其他所有的原始类型都有方法。作为一个Java开发者,我的理解是原始数据类型没有方法,只有类才有方法。但在C#中,以下代码是有效的:
string name = "alex";
Console.WriteLine(name.ToUpper());
这怎么可能?它们真的是原始类型吗?我在这里缺少了什么吗?

糟糕的例子,因为在Java中String是一个对象,所以"abc".toString()在Java中可以工作,就像在C#中"abc".ToString()一样。区别在于123.toString()在Java中不起作用,而123.ToString()在C#中可以工作。所以你可能会问为什么int(在C#中是原始类型)有方法。例如:Console.WriteLine(typeof(int).IsPrimitive) //true - barlop
1
如果不是原始类型,您可以将字符串视为“内在”类型。 - mckenzm
5个回答

34
string 在 C# 中不是一个原始类型,它是 C# 中两个预定义的引用类型之一(另一个是 object)。C# 中的原始类型包括 Booleanbool)、Bytebyte)、SBytesbyte)、Int16short)、UInt16Int32int)、UInt32uint)、Int64long)、UInt64ulong)、IntPtrUIntPtrCharchar)、Doubledouble)和 Singlesingle)。 请注意,规范说明“还可以使用结构体和运算符重载来实现 C# 语言中的新‘原始’类型”,但如果 MyStruct 是用户定义的 struct,则 typeof(MyStruct).IsPrimitivefalse
“我拿到了一本书,并开始阅读和练习代码。当我看到 C# 中的 string 被认为是一个原始类型时,我很惊讶。”
这是哪本书说的?
“简单明了地说,C# 和 Java 是不同的语言。在 C# 中,有一个称为 object 的概念,几乎所有内容都源于它(是的,有例外情况,最重要的是接口)。从 object 有一个派生类型叫做 ValueType。继承自 ValueType 的是具有值语义的 struct。所有其他派生自 object 的类型都是引用类型。所有这些 object 都封装了数据和行为(也就是说,它们可以拥有方法)。”
string name = "alex";” “Console.WriteLine(name.ToUpper());” “你对这段代码感到困惑吗?namestring 的实例,肯定被字符串字面量 "alex" 赋值,并且我们在 name 上调用了 String.ToUpper 方法的一种重载。然后调用接受 string 实例的 Console.WriteLine 的一种重载。你甚至可以这样做。”
Console.WriteLine("alex".ToUpper());

它们真的是原始类型吗?

不是。 string 不是原始类型。

我在这里错过了什么?

就是 C# 和 Java 是相关但非常不同的编程语言。


7
没错,经过简单的调查(并阅读了您在这里给出的所有答案),我发现C#中没有原始数据类型。我对代码不感到困惑,只是对允许在像int这样的类型中执行操作感到困惑。例如,int i = 5; i.ToString(); - Alex Ntousias
4
ToString是一个虚方法,在类object中定义,而ValueTypeInt32都继承自它(int是C#中Int32的保留关键字)。Int32提供了对ToString方法的重写。 - jason
1
+1 非常好的回答。(顺便说一下,这里是对你所说的“String不是原始类型”的快速验证:typeof(String).IsPrimitive - Andrew Hare
1
值得指出的是,int是一种原始类型,但在C#中,您可以说5.ToString(),而在Java中,您不能使用5.toString()。此外,在Java中,您可以执行"abc".toString(),就像C#中的"abc".ToString()一样。他显然对非原始类型具有方法的想法并不感到惊讶。他惊讶的是C#中的原始类型也可以这样做。 - barlop
1
C#和Java的主要区别在于C#具有统一的类型系统。所有类型都派生自System.Object,因此可以拥有字段/属性/方法,_除了_派生自System.Enum的类型(与支持枚举方法的Java相反)。内置/关键字类型和基元类型之间存在差异。基元是值类型,使用关键字本身作为存储。请参见Int64,使用long来存储值。 - mhand

17

string在C#中是一个类别名,实际上它是System.String类的别名。

然而,在.NET中所有类型都有方法。这是真正的面向对象编程,所有类都派生自System.Object,这也意味着System.Object的方法适用于C#中每种类型


4
小心!并不是所有东西都派生自 System.Object - jason
@Jason:感谢澄清!那么哪些不是从System.Object派生的呢? - Dirk Vollmar
2
@divo:例如指针类型和接口。 - jason
重要的是要注意,值类型存储位置与值类型实例具有不同的语义。 尽管 System.IntegerSystem.Object 继承,但类型为 System.Integer 的存储位置实际上不持有该类型的对象 - 它持有四个字节,这四个字节一起被解释为一个32位整数值。 同样,类型为 System.Drawing.Point 的存储位置实际上不持有该类型的对象 - 它持有四个整型字段 XYWidthHeight - supercat
@0xA3 是的,有趣的是在C#中你也可以对int类型这样做。在C#中,这个方法是可行的:123.ToString(),但在Java中,123.toString()是不行的。同样地,int a; 和执行a.ToString()或a.toString()也是一样的情况。在C#中它是可行的,但在Java中是不行的。虽然在C#中int被称为原始类型,但是Console.WriteLine(typeof(int).IsPrimitive)的输出结果可能表明C#对原始类型的定义与Java不同。 - barlop
显示剩余3条评论

7

简单来说,它们并不是真正的基本类型。编译器会将您的string替换为一个String对象。


string只是一个关键字,它是System.String的别名。 - jason
@Jason - 难道别名不就是这样吗?它实际上只是另一个“真实”事物的占位符名称吗?换句话说,你所说的逻辑上不就等同于编译器将string转换为System.String吗?现在我不确定它是在编译为CIL时发生还是在运行时JIT CIL时发生,但在某个时刻,string确实是System.String,对吧? - Alconja
我想我误解了你所说的“编译器将使用String对象替换你的string”。我以为你是在说这两者是不同的类型,而编译器只是在你说string时使用String,但现在我意识到(根据你的评论)这不是你的意思。对此我深表歉意;你的理解是正确的。它发生在编译时;也就是说,在你的代码中用string替换所有实例并用String替换或反之亦然,将会产生完全相同的IL。 - jason

6

5

1
字符串没有装箱/拆箱,它是一个引用类型。 - Bruno Reis
@Bruno Reis:感谢您的指出,我经常会忘记这一点,现在添加了完整的引用,以防日后对某些人有所帮助。 - Nick Craver
仍然错误:装箱是将值类型视为Object。如上所述,Int32与int相同。如果你将int“转换”为Int32,那么绝对不会发生装箱。装箱是转换为对象。您的列表描述了类型别名,但与装箱/拆箱完全无关。您应该复习这些概念。 - Bruno Reis
FYI,任何值类型都可能会进行装箱/拆箱,即使是您自己定义的“自定义”类型也是如此。您所描述的概念与装箱/拆箱完全无关。 - Bruno Reis
@Bruno Reis:谢谢!我一直以为它会在(Int32)int场景下拆箱并将其放在堆栈上进行方法调用,但我发现我们生成的IL并不是这样的...每天都会在这里学到新东西。我更新了这个问题,以防其他人以后也会遇到类似的情况。感谢您的教训,我相信它将有助于未来项目的开发。 - Nick Craver
@BrunoReis 当你将对象转换为接口时,就像将对象转换为object一样,会发生装箱。 - Jon Hanna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接