每当我尝试在C#或.net中搜索类和结构之间的差异时,我最终得到了两个方面的概念性概述,例如值类型或引用类型,其中变量被分配等等。但我需要一些实际差异。我发现一些像赋值运算符的不同行为,有构造函数等。任何人都可以提供一些更实际的差异,这些差异在编码时会直接有用吗?就像使用一个但不与其他或相同操作显示不同行为的东西一样。关于这两者的一些常见错误。
另外请建议在哪里考虑使用结构而不是类。并且不应该使用结构。
编辑: 我是否必须显式调用构造函数,或者只是声明一个struct类型变量就足够了?(我应该把它作为一个新问题吗?)
答案 0 :(得分:10)
好的,这里有一些具体的,实际的差异:
如果变量是类,则变量可以是 null ,但如果它是 struct ,则永远不会为空。
default(T)
为null
,但对于结构实际上构造了一个值(由许多二进制零组成)。
使用Nullable<T>
或T?
可以使结构成为可空。 类不能用于T
或Nullable<T>
中的T?
。
struct 始终有一个公共默认构造函数(一个零参数的构造函数)。程序员不能使用自定义实现覆盖此构造函数 - 它基本上是“一成不变”。 类允许程序员没有默认构造函数(或私有构造函数)。
类中的字段可以在其上声明默认值。在结构中,他们不能。
类可以从其他类继承,但 struct 不能声明为从任何内容派生(它隐式派生自System.ValueType
)。
在object.ReferenceEquals()
中使用类是有意义的,但使用 struct 变量将始终产生错误。
在lock()
语句中使用类是有意义的,但使用 struct 变量会导致非常微妙的失败。代码不会被锁定。
在32位系统上,理论上可以将最多536,870,912个引用的数组分配给类,但对于 struct < / strong>您需要考虑结构的大小,因为您正在分配实际的实例。
答案 1 :(得分:2)
只有在容器是内置数组时才能修改容器中的结构:
struct Point { public int x, y; void Move(int dx, int dy) { x += dx; y += dy; } }
...
Point[] points = getPointsArray();
points[0].Move(10, 0) = 10;
// points[0].x is now 10 higher.
List<Point> points = getPointsList();
points[0].Move(10, 0);
// No error, but points[0].x hasn't changed.
出于这个原因,我强烈赞成不可变结构:
Point Move(int dx, int dy) { return new Point(x + dx, y + dy); }
...
points[0] = points[0].Move(10, 0); // Always works.
一般观察:课程通常更好。当你想要保存小的,概念上原子的数据结构,如Point,Complex(number),Rational等时,结构优秀。
答案 2 :(得分:1)
结构,因为它们是值类型,在赋值时被复制;如果您创建自己的结构,则应使其不可变,请参阅Why are mutable structs evil?
答案 3 :(得分:1)
有时你不希望你所传递的内容变得可变,并且因为mutable
结构可能只是纯粹的邪恶,所以我避免创建一个:)这是一个例子:
class
版本:
class AccountInfo {
public string OwnerName { get; set; }
public string AccountNumber { get; set; }
}
struct
版本:
struct AccountInfo {
public string OwnerName;
public string AccountNumber;
}
现在想象你调用了这样一个方法:
public bool TransferMoney(AccountInfo from, AccountInfo to, decimal amount)
{
if(!IsAuthorized(from)) return false;
//Transfer money
}
struct
是Value type,意味着将副本传递给该方法。类版本意味着引用被传递到方法中,您不希望例如在授权通过后可以更改帐号,您希望在这样的操作中不需要更改任何内容。你想要一个不可变的值类型。 There's another question here asking why mutable structs are evil ...任何你不希望任何受参考对象影响的事情的操作都是struct
更适合的实际场所。
上面的示例可能有些愚蠢,但重点是任何敏感操作,其中传入的数据不应该在另一个线程中更改,或者通过任何方式确实是您看到的值传递的位置。
答案 4 :(得分:0)
分配它们(堆与堆栈)不是你在使用它们时真正关心的东西(不是你应该忽略它 - 你应该通过各种方法研究差异并理解它们)。
但是,当您决定用结构替换类时,您将遇到的最重要的实际区别是,结构通过值传递,而类实例通过传递参考
这意味着当您将结构传递给方法时,会创建其属性的副本(浅拷贝),并且您的方法实际上获得的副本与您在方法之外的副本不同。传递类的实例时,只会将对内存中相同位置的引用传递给该方法,然后您的方法将处理完全相同的数据。
例如,如果您有一个名为MyStruct
的结构和一个名为MyClass
的类,并将它们传递给此方法:
void DoSomething(MyStruct str, MyClass cls)
{
// this will change the copy of str, but changes
// will not be made to the outside struct
str.Something = str.Something + 1;
// this will change the actual class outside
// the method, because cls points to the
// same instance in memory
cls.Something = cls.Something + 1;
}
当方法结束时,您的类'属性将递增,但您的struct的属性将保持不变,因为str
方法中的DoSomething
变量不指向记忆中的同一个地方。
答案 5 :(得分:0)
非常重要的实际区别在于结构是值类型,而类是引用类型。这有一些影响。
首先,结构被复制。这两个代码块会有不同的结果(请注意,通常你既不应该使用公共字段也不应该使用可变结构,我这样做只是为了演示目的):
struct X
{
public int ID;
public string Name;
}
X x1 = new X { ID = 1, Name = "Foo" };
X x2 = x1;
x2.Name = "Bar";
Console.WriteLine(x1.Name); // Will print "Foo"
class Y
{
public int ID;
public string Name;
}
Y y1 = new Y { ID = 2, Name = "Bar" };
Y y2 = y1;
y2.Name = "Baz";
Console.WriteLine(y1.Name); // Will print "Baz"
除了X
是Y
之外, X
和struct
完全相同。结果不同,因为每次我们分配X
时,都会制作副本,如果我们更改副本,那么我们不会更改原始副本。另一方面,当我们将y1
的内容分配给y2
时,我们所做的就是复制一个引用; y1
和y2
都指的是内存中物理上相同的对象。
结构体作为值类型的第二个结果是通用约束。如果要传入值类型,则约束的名称实际上是“struct”或“class”:
public class MyGeneric<T>
where T : struct
{ ... }
以上内容可让您创建MyGeneric<int>
或MyGeneric<X>
,但不能创建MyGeneric<Y>
。另一方面,如果我们将其更改为where T : struct
,我们将不再允许创建前两个中的任何一个,但MyGeneric<Y>
是可以的。
最后但并非最不重要的是,在编写互操作时需要使用结构,因为使用结构,您可以保证在内存中的特定排列。
答案 6 :(得分:0)
Tejs提供的链接(http://www.jaggersoft.com/pubs/StructsVsClasses.htm)是一个很好的解释(虽然它有点过时,特别是对事件的解释)。
最重要的实际区别是struct是一个值类型,这意味着它是通过值而不是通过引用传递的。这真正意味着当一个struct作为参数传递时,它实际上是通过复制传递的。因此,对结构的一个实例的操作不会影响其他实例。
请考虑以下代码:
struct NumberStruct
{
public int Value;
}
class NumberClass
{
public int Value = 0;
}
class Test
{
static void Main()
{
NumberStruct ns1 = new NumberStruct();
NumberStruct ns2 = ns1;
ns2.Value = 42;
NumberClass nc1 = new NumberClass();
NumberClass nc2 = nc1;
nc2.Value = 42;
Console.WriteLine("Struct: {0}, {1}", ns1.Value, ns2.Value);
Console.WriteLine("Class: {0}, {1}", nc1.Value, nc2.Value);
}
}
由于ns1
和ns2
都属于NumberStruct
值类型,因此它们各自都有自己的存储位置,因此ns2.Number
的分配不会影响ns1.Number
。但是,由于nc1
和nc2
都是引用类型,nc2.Number
的分配确实会影响nc1.Number
的值,因为它们都包含相同的引用。
[免责声明:上述代码和文字取自Sams Teach Yourself Visual C# 2010 in 24 Hours]
此外,正如其他人已经指出的那样,结构应始终是不可变的。 (是的,在这个例子中,结构是可变的,但它是为了说明这一点。)部分原因是结构不应该包含公共字段。
由于结构是值类型,因此不能从结构继承。您也无法从基类派生结构。 (但结构可以实现接口。)
也不允许struct具有显式声明的public default(无参数)contstructor。您声明的任何其他构造函数必须完全初始化所有struct字段。结构也不能有明确声明的析构函数。
由于结构是值类型,因此它们不应实现IDisposable
,也不应包含非托管代码。
答案 7 :(得分:-1)
这是一个有趣的链接:http://www.jaggersoft.com/pubs/StructsVsClasses.htm
但是,在大多数情况下,当类为开发人员提供更多内容时,使用结构的可能性并不大。