.NET中的struct和class有什么区别?


Answers:


1055

在.NET中,有两类类型,引用类型值类型

结构是值类型,类是引用类型

通常的区别是,引用类型驻留在堆上,而值类型驻留在内联中,也就是说,无论它在何处定义了变量或字段。

包含值类型的变量包含整个值类型值。对于结构,这意味着变量包含整个结构及其所有字段。

包含引用类型的变量包含指针或对实际值所在的内存中其他位置的引用

首先有一个好处:

  • 值类型始终包含一个值
  • 引用类型可以包含null引用,这意味着它们目前根本不引用任何内容

在内部,引用类型 s被实现为指针,并且知道了这一点,并且知道了变量赋值的工作原理,还有其他行为模式:

  • 值类型变量的内容复制到另一个变量中,然后将整个内容复制到新变量中,以使两者截然不同。换句话说,复制后,对一个所做的更改不会影响另一个
  • 引用类型变量的内容复制到另一个变量中,然后复制引用,这意味着您现在在实际数据的其他存储位置拥有两个引用。换句话说,在复制之后,更改一个引用中的数据似乎也会影响另一个引用,但这仅仅是因为您实际上只是在两个地方查看相同的数据

声明变量或字段时,以下是两种类型的区别:

  • 变量:值类型驻留在堆栈上,引用类型驻留在堆栈上,作为指向实际内存所在的堆内存中某处的指针(尽管请注意Eric Lipperts文章系列:堆栈是实现细节)
  • class / struct-field:值类型完全位于该类型内部,引用类型位于该类型内部,作为指向实际内存所在的堆内存中某处的指针。

43
为了完整起见,我应该提到Eric Lippert曾说过堆栈是一个实现细节,每当我在上面提到堆栈时,都请记住Eric的帖子。
Lasse V. Karlsen

2
这对C ++也有效吗?
Koray Tugay 2012年

9
另一个关键的区别是用法。来自MSDN:“结构通常用于封装一小组相关变量,例如矩形坐标。结构还可以包含构造函数,常量,字段,方法,属性,索引器,运算符,事件和嵌套类型,尽管如果有多个这样的结构是必填项,则应考虑将您的类型设为类。”
thewpfguy

4
@KorayTugay不,不是。
ZoomIn

9
@KorayTugay在C ++结构和类中是绝对等效的,除了一件事-默认访问限制(默认情况下,类具有私有,结构具有公共)
berkus 2014年

207

每个的简短摘要:

仅课程:

  • 可以支持继承
  • 是引用(指针)类型
  • 引用可以为空
  • 每个新实例都有内存开销

仅结构:

  • 无法支持继承
  • 是值类型
  • 按值传递(如整数)
  • 不能有空引用(除非使用了Nullable)
  • 每个新实例没有内存开销-除非“装箱”

类和结构:

  • 复合数据类型通常用于包含一些具有某种逻辑关系的变量
  • 可以包含方法和事件
  • 可以支持接口

16
该答案的某些部分不太正确。类并不总是在堆上,而结构也不总是在堆上。当前的异常包括类上的struct字段,匿名方法和lambda表达式中捕获的变量,迭代器块以及已经提到的带框值。但是堆栈与堆的分配是一个实现细节,可能会发生变化。埃里克·利普帕特(Eric lippart)在这里对此进行讨论。我已投票,但如果您进行更新,会很乐意将其删除。
西蒙·史蒂文斯

1
结构不支持从其他结构/类继承,但是您可以在结构上实现接口。
thewpfguy

2
您可能想弄清楚当您声明结构“每个新实例没有内存开销”时的含义。我的第一个解释是,您声称-显然是荒谬的-结构使用零内存。然后,我想也许您是想说一个结构,与类不同,它所需要的内存恰好等于其成员字段之和,而不再需要更多。但是后来我用Google搜索c# struct memory overhead并找到了汉斯·帕桑特(Hans Passant)的答案,说不是,也不是。那么,什么你的意思是?
Mark Amery

4
@MarkAmery我对表达式“没有内存开销”的反应与您的id相同,但我认为OP指的是这样的事实:的实例class是托管内存(由垃圾收集器处理),而的实例struct则不是。
Hutch

1
“结构按值传递(如整数)”是错误的:所有变量都按值传递,也是引用类型。如果要通过引用传递变量,则必须使用“ ref”关键字。jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli,2017年

41

在.NET中,结构和类声明区分引用类型和值类型。

当您绕过一种引用类型时,实际上仅存储一种。所有访问该实例的代码都在访问同一代码。

当您传递一个值类型时,每个值类型都是一个副本。所有代码都在自己的副本上工作。

可以用一个示例显示:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

对于一堂课,这将是不同的

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

类不能为空-引用可以指向null。

结构是实际值-它们可以为空,但不能为null。因此,结构始终具有不带参数的默认构造函数-它们需要一个“起始值”。


@ T.Todua是的,上面有更好的答案,我投票赞成并提供了这个答案,这是从SO的早期beta版本开始的,当时我们仍在弄清楚规则。
基思(Keith)

1
我不知道您是否正确理解了我,我真的赞成/接受您的答案(与上述答案相反),因为您有很好的例子(不仅是理论解释,与以上答案相反,后者只有理论解释而没有例子) )。
T.Todua

23

结构和类之间的区别:

  • 结构是值类型,类是引用类型
  • 结构存储在堆栈中,类存储在堆中
  • 值类型将它们的值保存在声明它们的内存中,但是引用类型保存对对象内存的引用。
  • 值类型在范围丢失后立即销毁,而引用类型仅变量在范围丢失后销毁。该对象随后被垃圾收集器破坏。
  • 当您将一个结构复制到另一个结构中时,该结构的新副本将被创建并修改为一个结构,而不会影响另一个结构的值。
  • 当您将一个类复制到另一个类时,它仅复制引用变量。
  • 这两个引用变量都指向堆上的同一对象。更改为一个变量将影响另一个参考变量。
  • 结构不能有析构函数,但类可以有析构函数。
  • 结构不能具有显式的无参数构造函数,而可以构造的类不支持继承,但是类可以。两者都支持从接口继承。
  • 结构为密封型

21

从Microsoft 在类和结构之间选择 ...

根据经验,框架中的大多数类型应为类。但是,在某些情况下,值类型的特征使其更适合使用结构。

考虑结构而不是类:

  • 如果类型的实例很小并且通常是短暂的,或者通常嵌入在其他对象中。

X 避免使用结构,除非类型具有以下所有特征:

  • 它在逻辑上表示一个值,类似于基本类型(int,double等)。
  • 它的实例大小小于16个字节。
  • 这是一成不变的。(无法更改)
  • 不必经常装箱。

19

除了其他答案中描述的所有差异之外:

  1. 结构不能具有显式的无参数构造函数,而类可以
  2. 结构不能有析构函数,而类可以有析构函数
  3. 结构不能从另一个结构或类继承,而一个类可以从另一个类继承。(结构和类都可以从接口实现。)

如果您正在观看解释所有差异的视频,则可以查看第29部分-C#教程-C#中的类和结构之间的差异


4
.net语言通常不允许结构定义无参数构造函数(由语言编译器决定是否允许无参数构造函数)这一事实要重要得多的事实是,结构可以存在并可以公开无需运行任何构造函数即可访问外部世界(即使定义了无参数构造函数)。.net语言通常禁止为结构使用无参数构造函数的原因是为了避免由于有时运行这种构造函数而有时不运行这种构造函数而造成的混乱。
supercat 2012年

15

类的实例存储在托管堆上。所有“包含”实例的变量只是对堆上实例的引用。将对象传递给方法会导致传递引用的副本,而不是对象本身。

结构(从技术上讲,值类型)存储在任何使用它们的位置,就像原始类型一样。内容可以在运行时随时复制,而无需调用自定义的复制构造函数。将值类型传递给方法涉及再次复制整个值,而无需调用任何可自定义的代码。

C ++ / CLI名称使区分更好:“ ref class”是第一个描述的类,“ value class”是第二个描述的类。C#使用的关键字“类”和“结构”只是必须学习的东西。


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
这实际上是非常出色的:总结性和信息性。请仅记住至少一次校对您的答案-您在某些行中交换了struct和class的解释,也有一些错别字。
罗伯特·科佩奇(RobertKopeć)

1
@ensisNoctis对不起,感谢您的修改。我应该重新阅读我的回答😅–
0xaryan

8

结构与类别

结构是值类型,因此它存储在堆栈中,而类是引用类型,并存储在堆中。

结构不支持继承和多态,但是类同时支持。

默认情况下,所有结构成员都是公共的,但类成员默认情况下是私有的。

由于结构是值类型,因此我们不能将NULL赋给struct对象,但类并非如此。


5
关于“所有结构成员都是公开的”:如果我没记错的话,那是不对的。“默认情况下,类成员和结构成员(包括嵌套的类和结构)的访问级别是私有的。” msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

除了其他答案外,还有一个基本区别值得注意,那就是数据在数组内的存储方式,因为这会对性能产生重大影响。

  • 对于结构,数组包含该结构的实例
  • 对于类,数组包含指向内存中其他位置的类实例的指针

所以内存中的结构数组看起来像这样

[struct][struct][struct][struct][struct][struct][struct][struct]

而一组类看起来像这样

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

对于一组类,您感兴趣的值不会存储在数组中,而是会存储在内存中的其他位置。

对于绝大多数应用程序而言,这种区别并不重要,但是,在高性能代码中,这将影响内存中数据的位置,并严重影响CPU缓存的性能。在可以/应该使用结构的情况下使用类将大大增加CPU上的高速缓存未命中数。

现代CPU最慢的操作不是处理数字,而是从内存中获取数据,一级缓存命中率比从RAM中读取数据快许多倍。

这是您可以测试的一些代码。在我的机器上,遍历类数组花费的时间比struct数组长约3倍。

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

-1; “结构是值类型,因此它们存储值,类是引用类型,因此它们引用类。” 尚不清楚,而且对于从此处其他答案尚未理解的任何人来说都不太有意义,并且“对于一个类,包含类将只在不同的内存区域中包含指向新类的指针。” 将类与类实例混淆。
Mark Amery

@MarkAmery我试图稍微澄清一下。我真正想说明的是,数组与值和引用类型的工作方式以及对性能的影响之间的差异。我并没有试图重新解释什么是值和引用类型,因为这样做有很多其他答案。
Will Calderwood '18

7

只是为了使其完整,使用该Equals方法还有另一个区别,该方法被所有类和结构继承。

假设我们有一个类和一个结构:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

在Main方法中,我们有4个对象。

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

然后:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

因此,结构适用于类似数字的对象,例如点(保存x和y坐标)。而且班级适合其他人。即使2个人的名字,身高,体重...相同,他们仍然是2个人。


6

好吧,对于初学者来说,结构是通过值而不是通过引用传递的。结构适合于相对简单的数据结构,而从架构的角度来看,类通过多态性和继承具有更大的灵活性。

其他人可能会给您比我更多的细节,但是当我要使用的结构很简单时,我会使用结构。


4

除了访问说明符的基本区别之外,我还想补充一些主要区别,包括上面提到的少量区别和带有输出的代码示例,这将使参考和值的概念更加清晰。

结构:

  • 是值类型,不需要堆分配。
  • 内存分配不同,并存储在堆栈中
  • 对于小型数据结构很有用
  • 影响性能,当我们将值传递给方法时,我们传递整个数据结构并将所有传递给堆栈。
  • 构造函数只返回结构值本身(通常在堆栈的临时位置),然后根据需要复制此值
  • 每个变量都有自己的数据副本,并且对一个变量的操作不可能影响另一个变量。
  • 不支持用户指定的继承,它们从类型object隐式继承

类:

  • 参考类型值
  • 存储在堆中
  • 存储对动态分配对象的引用
  • 构造函数由new运算符调用,但不会在堆上分配内存
  • 多个变量可能引用同一对象
  • 一个变量的操作可能会影响另一变量引用的对象

代码样例

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

输出量

Struct对象的初始值为:10

内部结构方法内部结构方法的内部方法值是:20

结构对象的Method调用值之后:10

类对象的初始值为:10

内部类方法内部类方法的内部方法值是:20

在Class Object的Method调用值之后为:20

在这里,您可以清楚地看到按值调用和按引用调用之间的区别。


4
  1. 在类中声明的事件具有通过锁自动锁定的+ =和-=访问权限,从而使它们成为线程安全的(静态事件在类的类型上锁定)。在结构中声明的事件没有自动锁定其+ =和-=访问。由于只能锁定引用类型表达式,因此结构的锁(this)将不起作用。

  2. 创建结构实例不会导致垃圾回收(除非构造函数直接或间接创建引用类型实例),而创建引用类型实例则会导致垃圾回收。

  3. 结构始终具有内置的公共默认构造函数。

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

    这意味着结构始终是可实例化的,而类可能不是可实例化的,因为其所有构造函数都可以是私有的。

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. 结构不能有析构函数。析构函数只是对象的替代,变相完成,结构(即值类型)不受垃圾回收的限制。

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. 结构是隐式密封的,而类则不是。
    结构不能抽象,类可以抽象。
    构造函数不能在其构造函数中调用:base(),而没有显式基类的类则可以。
    一个结构不能扩展另一个类,一个类可以。
    结构不能声明类可以保护的成员(例如,字段,嵌套类型)。
    结构不能声明抽象函数成员,而抽象类则可以。
    结构不能声明虚拟函数成员,类可以声明。
    结构不能声明密封函数成员,而类则可以。
    结构不能声明重写函数成员,而类则可以。
    该规则的一个例外是结构可以覆盖System.Object,viz,Equals()和GetHashCode()和ToString()的虚拟方法。


在什么情况下会使用带有结构的事件?我可以想象,一个非常精心编写的程序可以以某种可行的方式使用带有结构的事件,但前提是该结构永远不会被值复制或传递,在这种情况下,它也可能是一个类。
supercat

@supercat是的,结构中的非静态事件将非常奇怪,并且仅对可变结构有用,并且事件本身(如果它是“类似字段的”事件)会将结构转变为“可变”类别,并在结构中引入引用类型的字段。结构中的非静态事件一定是邪恶的。
Jeppe Stig Nielsen 2014年

@JeppeStigNielsen:唯一可以看到结构发生事件的地方的模式就是结构的目的是保持对作为代理的类对象的不可变引用。但是,在这种情况下,自动事件将完全没有用。相反,必须将订阅和取消订阅事件中继到该结构后面的类。我希望.NET有(或才有可能定义)一个“缓存盒子”结构类型与类型的最初空的隐藏字段Object,将举行一个参考结构的盒装拷贝。
supercat

1
@JeppeStigNielsen:在许多代理使用情况下,结构的性能均优于类;使用结构的最大问题是,在需要装箱的情况下,装箱经常最终会推迟到内部循环中。如果有一种方法可以避免结构重复装箱,那么在许多使用场景中,它们将比类更好。
超级猫

4

如前所述:类是引用类型,而结构是具有所有后果的值类型。

根据规则,框架设计指南建议在以下情况下使用结构而不是类:

  • 实例大小小于16个字节
  • 它在逻辑上表示一个值,类似于基本类型(int,double等)。
  • 这是一成不变的
  • 不必经常装箱

3

有一种有趣的“类与结构”难题的情况-当您需要从该方法返回多个结果时的情况:选择要使用的结果。如果您了解ValueTuple的故事-您知道添加了ValueTuple(结构)是因为它比Tuple(类)更有效。但是,这在数字上是什么意思呢?两项测试:一种是具有2个字段的struct / class,另一种是具有8个字段的struct / class(维数大于4-类应该比处理器滴答更有效,但当然也应考虑GC负载) )。

PS还有一个针对特定案例“带有集合的类或类”的基准:https ://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

代码测试:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

2

结构是实际值-它们可以为空,但不能为null

的确如此,但是还请注意,从.NET 2开始,结构支持Nullable版本,并且C#提供了一些语法糖以使其易于使用。

int? value = null;
value  = 1;

1
请注意,这只是语法糖,其读取为“ Nullable <int> value = null;”。
Erik van Brakel

@ErikvanBrakel不只是语法糖。不同的装箱规则意味着(object)(default(int?)) == null您不能使用任何其他值类型,因为这里不仅有糖。唯一的糖是int?Nullable<int>
乔恩·汉纳

-1; 这没有解决结构和类之间的区别是什么的问题,因此,它应该是对您要回答的答案的评论,而不是单独的答案。(尽管网站规范可能与2008年8月有所不同!)
Mark Amery

1

基本值类型或结构类型的每个变量或字段都具有该类型的唯一实例,包括其所有字段(公共字段和私有字段)。相比之下,引用类型的变量或字段可以为null,或者可以引用存储在其他位置的对象,也可以存在许多其他引用。结构的字段将与该结构类型的变量或字段存储在同一位置,该变量或字段可以在堆栈上,也可以另一个堆对象的一部分

创建原始值类型的变量或字段将使用默认值创建它;创建结构类型的变量或字段将创建一个新实例,并以默认方式在其中创建所有字段。创建引用类型的新实例将首先以默认方式在其中创建所有字段,然后根据类型运行可选的附加代码。

将一个基本类型的变量或字段复制到另一个将复制该值。将一个变量或结构类型的字段复制到另一个变量或字段将把前一个实例的所有字段(公共和私有)都复制到后一个实例。将一个变量或引用类型的字段复制到另一个变量或字段将导致后者引用与前者相同的实例(如果有)。

需要特别注意的是,在某些语言(如C ++)中,类型的语义行为与存储方式无关,但是.NET并非如此。如果某个类型实现了可变值语义,则将该类型的一个变量复制到另一个变量中,会将第一个实例的属性复制到另一个实例,该实例由第二个实例引用,并使用第二个实例的成员进行变异,这将导致第二个实例被更改,但不是第一个。如果一个类型实现了可变的引用语义,则将一个变量复制到另一个变量并使用第二个变量的成员来对该对象进行变异将影响第一个变量所引用的对象;具有不可变语义的类型不允许突变,因此在语义上复制是创建新实例还是创建对第一个实例的另一个引用都无关紧要。

在.NET中,值类型可以实现上面的任何语义,条件是它们的所有字段都可以照此执行。但是,引用类型只能实现可变的引用语义或不可变语义。具有可变引用类型字段的值类型仅限于实现可变引用语义或奇怪的混合语义。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.