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

时间:2008-08-16 08:21:48

标签: .net class struct value-type reference-type

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

19 个答案:

答案 0 :(得分:954)

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

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

一般区别是引用类型存在于堆上,值类型生活在内联中,也就是说,只要定义了变量或字段。

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

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

这有一个好处,首先是:

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

在内部,引用类型被实现为指针,并且知道并且知道变量赋值如何工作,还有其他行为模式:

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

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

  • 变量:值类型存在于堆栈中,引用类型作为指向堆内存中某处的实际内存所在的指针(尽管注意{{ 3}}。)
  • class / struct-field:值类型完全位于类型内部,引用类型位于类型内部,作为指向堆内存中某处实际内存的指针

答案 1 :(得分:178)

每个的简短摘要:

仅限课程:

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

仅限结构:

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

类和结构:

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

答案 2 :(得分:32)

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

当您绕过引用类型时,实际只存储了一个。访问该实例的所有代码都访问同一个代码。

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

这可以通过示例显示:

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。

结构是实际值 - 它们可以为空但从不为空。由于这个原因,结构总是有一个没有参数的默认构造函数 - 它们需要一个'起始值'。

答案 3 :(得分:18)

来自微软的Choosing Between Class and Struct ...

  

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

     

考虑结构而不是类:

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

X 避免结构,除非该类型具有以下全部   特性:

     
      
  • 它逻辑上表示单个值,类似于原始类型(int,double等)。
  •   
  • 它的实例大小小于16个字节。
  •   
  • 这是不可改变的。 (无法更改)
  •   
  • 不必频繁装箱。
  •   

答案 4 :(得分:18)

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

  1. 结构cannot have an explicit parameterless constructor而类可以
  2. 结构cannot have destructors,而类可以
  3. 从另一个结构或类中构造can't inherit,而类可以从另一个类继承。 (结构和类都可以从接口实现。)
  4. 如果您正在浏览解释所有差异的视频,则可以查看 Part 29 - C# Tutorial - Difference between classes and structs in C#

答案 5 :(得分:14)

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

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

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

答案 6 :(得分:14)

结构和类之间的区别:

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

答案 7 :(得分:9)

我♥可视化,这里我创建了一个可视化,以显示structsclasses之间的基本区别。 enter image description here


有关更多信息,请参见以下内容:

答案 8 :(得分:6)

结构与类

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

结构不支持继承和多态,但是类支持两者。

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

由于结构是值类型,我们不能将null赋给结构对象,但对于类来说则不是这样。

答案 9 :(得分:6)

为了使其完整,使用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个人。

答案 10 :(得分:5)

要添加其他答案,有一个值得注意的根本区别,那就是它如何存储在内存中。这可能会对阵列的性能产生重大影响。结构是值类型,因此它们将值存储在它们指向的内存区域中,类是引用类型,因此它们引用它们指向的内存区域中的类,实际值存储在其他位置。 / p>

  • 使用结构,在包含的类中分配内存以存储数据。
  • 对于一个类,包含类将只包含指向不同内存区域中新类的指针。

对于数组也是如此,因此结构数组在内存中看起来像这样

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

类的数组看起来像这样

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

您感兴趣的实际值实际上并不存储在数组中,而是存储在内存中。

对于绝大多数应用程序而言,这种差异并不重要,但是,在高性能代码中,这会影响内存中数据的位置,并对CPU缓存的性能产生很大影响。在可能/应该使用结构时使用类将大大增加CPU上的高速缓存未命中数。

现代CPU所做的最慢的事情不是紧急数字,它是从内存中获取数据,而L1缓存命中比从RAM读取数据快许多倍。

答案 11 :(得分:4)

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

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

答案 12 :(得分:3)

除了访问说明符的基本区别,以及上面提到的几个差异之外,我想补充一些主要的差异,包括上面提到的几个与带有输出的代码示例,这将更清楚地了解参考和值

结构:

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

<强>类别:

  • 参考类型值
  • 存储在堆
  • 存储对动态分配的对象的引用
  • 使用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 Object的初始值为:10

内部结构方法 Struct Object的Inside Method值为:20

结构对象的方法调用值为:10

Class Object的初始值为:10

内部类方法 Class Object的Inside Method值为:20

类对象的方法调用值为:20

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

答案 13 :(得分:3)

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

作为规则的缩影框架设计指南建议在以下情况下使用Structs而不是类:

  • 实例大小小于16字节
  • 它逻辑上表示单个值,类似于原始类型(int,double等)
  • 这是不可改变的
  • 不必频繁装箱

答案 14 :(得分:3)

  1. 在类中声明的事件通过锁(this)自动锁定其+ =和 - =访问权限,以使其线程安全(静态事件在类的类型上被锁定)。在结构中声明的事件没有自动锁定其+ =和 - =访问权限。结构的锁(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(),而没有显式基类的类可以。
    结构不能扩展另一个类,类可以。
    结构不能声明类可以保护受保护的成员(例如,字段,嵌套类型)。
    结构不能声明抽象函数成员,抽象类可以。
    结构不能声明虚函数成员,类可以。
    结构不能声明密封的函数成员,类可以。
    struct不能声明覆盖函数成员,类可以。
    此规则的一个例外是结构可以覆盖System.Object的虚方法,即Equals(),GetHashCode()和ToString()。

答案 15 :(得分:2)

+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                       |                                                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                                                                        |
| Access Modifiers      | public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

答案 16 :(得分:1)

  

结构是实际值 - 它们可以为空但从不为空

这是事实,但是请注意,从.NET 2开始,结构体支持Nullable版本,C#提供一些语法糖,使其更易于使用。

int? value = null;
value  = 1;

答案 17 :(得分:1)

There is one interesting case of "class vs struct" puzzle - situation when you need to return several results from the method: choose which to use. If you know the ValueTuple story - you know that ValueTuple (struct) was added because it should be more effective then Tuple (class). But what does it mean in numbers? Two tests: one is struct/class that have 2 fields, other with struct/class that have 8 fields (with dimension more then 4 - class should become more effective then struct in terms processor ticks, but of course GC load also should be considered).

P.S. Another benchmark for specific case 'sturct or class with collections' is there: https://stackoverflow.com/a/45276657/506147

public GetQuoteResponse getQuote(String ticker) {

    GetQuote request = new GetQuote();
    request.setSymbol(ticker);

    log.info("Requesting quote for " + ticker);

    GetQuoteResponse response = (GetQuoteResponse) getWebServiceTemplate()
            .marshalSendAndReceive("http://www.webservicex.com/stockquote.asmx",
                    request,
                    new SoapActionCallback("http://www.webserviceX.NET/GetQuote"));

    return response;
}

Code test:

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 |

答案 18 :(得分:0)

原始值类型或结构类型的每个变量或字段都包含该类型的唯一实例,包括其所有字段(公共和私有)。相反,引用类型的变量或字段可以保持为空,或者可以指代存储在别处的对象,也可以存在任何数量的其他引用。 struct的字段将存储在与该结构类型的变量或字段相同的位置,该变量或字段可以在堆栈上,也可以是 另一个堆对象的一部分。

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

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

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

在.NET中,值类型可以实现上述任何语义,前提是它们的所有字段都可以这样做。但是,引用类型只能实现可变引用语义或不可变语义;具有可变引用类型字段的值类型仅限于实现可变引用语义或奇怪的混合语义。