Câu trả lời:
Trong .NET, có hai loại loại, loại tham chiếu và loại giá trị .
Cấu trúc là các loại giá trị và các lớp là các loại tham chiếu .
Sự khác biệt chung là một loại tham chiếu sống trên heap và một loại giá trị sống nội tuyến, nghĩa là, bất cứ nơi nào nó là biến hoặc trường của bạn được xác định.
Một biến chứa một loại giá trị chứa toàn bộ giá trị loại giá trị. Đối với một cấu trúc, điều đó có nghĩa là biến chứa toàn bộ cấu trúc, với tất cả các trường của nó.
Một biến chứa kiểu tham chiếu chứa con trỏ hoặc tham chiếu đến một nơi khác trong bộ nhớ có giá trị thực tế nằm trong đó.
Điều này có một lợi ích, để bắt đầu với:
Trong nội bộ, các kiểu tham chiếu được triển khai như các con trỏ và biết rằng, và biết cách gán biến hoạt động, có các mẫu hành vi khác:
Khi bạn khai báo các biến hoặc các trường, đây là cách hai loại khác nhau:
Một bản tóm tắt ngắn của mỗi:
Chỉ các lớp học:
Cấu trúc chỉ:
Cả hai lớp và cấu trúc:
c# struct memory overhead
và tìm thấy câu trả lời này của Hans Passant nói rằng không, đó cũng không phải là trường hợp. Vậy điều gì làm bạn nghĩa là gì?
class
bộ nhớ được quản lý (được xử lý bởi trình thu gom rác), trong khi các trường hợp struct
không phải là .
Trong .NET, các khai báo struct và class phân biệt giữa các loại tham chiếu và các loại giá trị.
Khi bạn vượt qua vòng một loại tham chiếu, chỉ có một loại thực sự được lưu trữ. Tất cả các mã truy cập thể hiện đang truy cập cùng một mã.
Khi bạn vượt qua vòng một loại giá trị, mỗi loại là một bản sao. Tất cả các mã đang làm việc trên bản sao của chính nó.
Điều này có thể được hiển thị với một ví dụ:
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.
Đối với một lớp học, điều này sẽ khác
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.
Các lớp có thể không có gì - tham chiếu có thể trỏ đến null.
Cấu trúc là giá trị thực tế - chúng có thể trống nhưng không bao giờ rỗng. Vì lý do này, các cấu trúc luôn có một hàm tạo mặc định không có tham số - chúng cần một 'giá trị bắt đầu'.
Sự khác biệt giữa Structs và Classes:
Từ lựa chọn của Microsoft giữa Class và Struct ...
Theo nguyên tắc thông thường, phần lớn các loại trong khung nên là các lớp. Tuy nhiên, có một số tình huống trong đó các đặc tính của một loại giá trị làm cho việc sử dụng các cấu trúc phù hợp hơn.
✓ KIỂM TRA một cấu trúc thay vì một lớp:
- Nếu các thể hiện của loại nhỏ và thường tồn tại trong thời gian ngắn hoặc thường được nhúng trong các đối tượng khác.
X TRÁNH một cấu trúc trừ khi loại có tất cả các đặc điểm sau:
- Nó đại diện một cách hợp lý một giá trị duy nhất, tương tự như các kiểu nguyên thủy (int, double, v.v.).
- Nó có kích thước cá thể dưới 16 byte.
- Nó là bất biến. (không thể thay đổi)
- Nó sẽ không phải được đóng hộp thường xuyên.
Ngoài tất cả các khác biệt được mô tả trong các câu trả lời khác:
Nếu bạn đang theo dõi một video giải thích tất cả sự khác biệt, bạn có thể xem Phần 29 - Hướng dẫn C # - Sự khác biệt giữa các lớp và cấu trúc trong C # .
Thể hiện của các lớp được lưu trữ trên heap được quản lý. Tất cả các biến 'chứa' một thể hiện chỉ đơn giản là một tham chiếu đến thể hiện trên heap. Truyền một đối tượng cho một phương thức dẫn đến một bản sao của tham chiếu được truyền, chứ không phải chính đối tượng đó.
Các cấu trúc (về mặt kỹ thuật, các loại giá trị) được lưu trữ bất cứ nơi nào chúng được sử dụng, giống như một kiểu nguyên thủy. Các nội dung có thể được sao chép bởi bộ thực thi bất cứ lúc nào và không cần gọi một hàm tạo sao chép tùy chỉnh. Truyền một loại giá trị cho một phương thức liên quan đến việc sao chép toàn bộ giá trị, một lần nữa mà không cần gọi bất kỳ mã tùy chỉnh nào.
Sự khác biệt được làm cho tốt hơn bởi các tên C ++ / CLI: "lớp ref" là một lớp như được mô tả trước, "lớp giá trị" là một lớp như được mô tả thứ hai. Các từ khóa "lớp" và "struct" được sử dụng bởi C # đơn giản là thứ phải học.
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
Cấu trúc so với lớp
Cấu trúc là một loại giá trị vì vậy nó được lưu trữ trên ngăn xếp, nhưng một lớp là một kiểu tham chiếu và được lưu trữ trên heap.
Một cấu trúc không hỗ trợ kế thừa và đa hình, nhưng một lớp hỗ trợ cả hai.
Theo mặc định, tất cả các thành viên cấu trúc là công khai nhưng các thành viên lớp theo mặc định là riêng tư.
Vì cấu trúc là một loại giá trị, chúng ta không thể gán null cho một đối tượng struct, nhưng nó không phải là trường hợp của một lớp.
Để thêm vào các câu trả lời khác, có một sự khác biệt cơ bản đáng chú ý và đó là cách dữ liệu được lưu trữ trong các mảng vì điều này có thể có ảnh hưởng lớn đến hiệu suất.
Vì vậy, một loạt các cấu trúc trông như thế này trong bộ nhớ
[struct][struct][struct][struct][struct][struct][struct][struct]
Trong khi đó một mảng các lớp trông như thế này
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
Với một mảng các lớp, các giá trị bạn quan tâm không được lưu trữ trong mảng, mà ở các nơi khác trong bộ nhớ.
Đối với phần lớn các ứng dụng, sự khác biệt này không thực sự quan trọng, tuy nhiên, trong mã hiệu suất cao, điều này sẽ ảnh hưởng đến vị trí dữ liệu trong bộ nhớ và có ảnh hưởng lớn đến hiệu suất của bộ đệm CPU. Sử dụng các lớp khi bạn có thể / nên sử dụng các cấu trúc sẽ tăng ồ ạt số lượng bộ nhớ cache trên CPU.
Điều chậm nhất mà CPU hiện đại không làm hỏng số, đó là tìm nạp dữ liệu từ bộ nhớ và lần truy cập bộ đệm L1 nhanh hơn nhiều lần so với đọc dữ liệu từ RAM.
Đây là một số mã bạn có thể kiểm tra. Trên máy của tôi, việc lặp qua mảng lớp sẽ dài hơn ~ 3 lần so với mảng struct.
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}");
}
Chỉ cần làm cho nó hoàn chỉnh, có một sự khác biệt khác khi sử dụng Equals
phương thức, được kế thừa bởi tất cả các lớp và cấu trúc.
Hãy nói rằng chúng ta có một lớp và một cấu trúc:
class A{
public int a, b;
}
struct B{
public int a, b;
}
và trong phương thức Main, chúng ta có 4 đối tượng.
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;
}
Sau đó:
s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false
Vì vậy , các cấu trúc phù hợp với các đối tượng giống như số, như các điểm (lưu tọa độ x và y). Và các lớp học phù hợp cho những người khác. Ngay cả khi 2 người có cùng tên, chiều cao, cân nặng ..., họ vẫn là 2 người.
Vâng, đối với người mới bắt đầu, một cấu trúc được truyền bằng giá trị chứ không phải bằng tham chiếu. Các cấu trúc tốt cho các cấu trúc dữ liệu tương đối đơn giản, trong khi các lớp có tính linh hoạt hơn rất nhiều từ quan điểm kiến trúc thông qua tính đa hình và kế thừa.
Những người khác có thể có thể cung cấp cho bạn nhiều chi tiết hơn tôi, nhưng tôi sử dụng các cấu trúc khi cấu trúc mà tôi sẽ đơn giản.
Bên cạnh sự khác biệt cơ bản của trình xác định truy cập và một vài điều được đề cập ở trên, tôi muốn thêm một số khác biệt chính bao gồm một vài trong số các đề cập ở trên với một mẫu mã có đầu ra, điều này sẽ cho ý tưởng rõ ràng hơn về tham chiếu và giá trị
Cấu trúc:
Lớp học:
Mẫu mã
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;
}
}
Đầu ra
Giá trị ban đầu của đối tượng Struct là: 10
Phương thức bên trong Giá trị phương thức bên trong Giá trị phương thức của đối tượng Struct là: 20
Giá trị cuộc gọi Phương thức của đối tượng Struct là: 10
Giá trị ban đầu của Class Object là: 10
Phương thức bên trong Giá trị phương thức bên trong của Đối tượng lớp là: 20
Giá trị cuộc gọi Phương thức của Đối tượng lớp là: 20
Ở đây bạn có thể thấy rõ sự khác biệt giữa cuộc gọi theo giá trị và cuộc gọi theo tham chiếu.
Các sự kiện được khai báo trong một lớp có quyền truy cập + = và - = của chúng tự động bị khóa thông qua khóa (điều này) để làm cho chúng an toàn theo luồng (các sự kiện tĩnh được khóa trên kiểu của lớp). Các sự kiện được khai báo trong một cấu trúc không có quyền truy cập + = và - = của chúng tự động bị khóa. Một khóa (cái này) cho một cấu trúc sẽ không hoạt động vì bạn chỉ có thể khóa trên một biểu thức kiểu tham chiếu.
Tạo một cá thể cấu trúc không thể gây ra một bộ sưu tập rác (trừ khi hàm tạo trực tiếp hoặc gián tiếp tạo một thể hiện kiểu tham chiếu) trong khi tạo một thể hiện kiểu tham chiếu có thể gây ra bộ sưu tập rác.
Một cấu trúc luôn có một hàm tạo mặc định công khai tích hợp.
class DefaultConstructor
{
static void Eg()
{
Direct yes = new Direct(); // Always compiles OK
InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
//...
}
}
Điều này có nghĩa là một cấu trúc luôn luôn khả thi trong khi một lớp có thể không vì tất cả các hàm tạo của nó có thể là riêng tư.
class NonInstantiable
{
private NonInstantiable() // OK
{
}
}
struct Direct
{
private Direct() // Compile-time error
{
}
}
Một cấu trúc không thể có một hàm hủy. Một hàm hủy chỉ là một phần ghi đè của object.Finalize trong ngụy trang và các cấu trúc, là các loại giá trị, không phải là đối tượng thu gom rác.
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
Một cấu trúc được ngầm kín, một lớp không.
Một cấu trúc không thể trừu tượng, một lớp có thể.
Một struct không thể gọi: base () trong hàm tạo của nó trong khi một lớp không có lớp cơ sở rõ ràng có thể.
Một cấu trúc không thể mở rộng một lớp khác, một lớp có thể.
Một cấu trúc không thể khai báo các thành viên được bảo vệ (ví dụ: các trường, các kiểu lồng nhau) một lớp có thể.
Một cấu trúc không thể khai báo các thành viên hàm trừu tượng, một lớp trừu tượng có thể.
Một cấu trúc không thể khai báo các thành viên hàm ảo, một lớp có thể.
Một cấu trúc không thể khai báo các thành viên chức năng niêm phong, một lớp có thể.
Một cấu trúc không thể khai báo các thành viên hàm ghi đè, một lớp có thể.
Một ngoại lệ cho quy tắc này là một cấu trúc có thể ghi đè các phương thức ảo của System.Object, viz, Equals () và GetHashCode () và ToString ().
Object
, sẽ giữ tham chiếu đến bản sao được đóng hộp của cấu trúc.
Như đã đề cập trước đây: Các lớp là loại tham chiếu trong khi Structs là loại giá trị với tất cả các hậu quả.
Như một ngón tay cái của Nguyên tắc Thiết kế Khung Quy tắc khuyến nghị sử dụng Structs thay vì các lớp nếu:
Có một trường hợp thú vị của câu đố "class vs struct" - tình huống khi bạn cần trả về một số kết quả từ phương thức: chọn sử dụng. Nếu bạn biết câu chuyện ValueTuple - bạn biết rằng ValueTuple (struct) đã được thêm vào vì nó sẽ hiệu quả hơn thì Tuple (class). Nhưng nó có nghĩa gì trong số? Hai bài kiểm tra: một là struct / class có 2 trường, một trường có struct / class có 8 trường (với thứ nguyên nhiều hơn 4 lớp sẽ trở nên hiệu quả hơn sau đó cấu trúc theo thuật ngữ tick bộ xử lý, nhưng tất nhiên cũng nên xem xét tải GC ).
PS Một điểm chuẩn khác cho trường hợp cụ thể 'sturct hoặc class with Collection' là: 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 |
Kiểm tra mã:
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;
}
}
}
}
Cấu trúc là giá trị thực - chúng có thể trống nhưng không bao giờ rỗng
Điều này là đúng, tuy nhiên cũng lưu ý rằng các cấu trúc .NET 2 hỗ trợ phiên bản Nullable và C # cung cấp một số đường cú pháp để giúp sử dụng dễ dàng hơn.
int? value = null;
value = 1;
(object)(default(int?)) == null
bạn không thể làm gì với bất kỳ loại giá trị nào khác, bởi vì có nhiều thứ hơn là đường đang diễn ra ở đây. Đường duy nhất là int?
cho Nullable<int>
.
Mỗi biến hoặc trường của loại giá trị nguyên thủy hoặc loại cấu trúc chứa một thể hiện duy nhất của loại đó, bao gồm tất cả các trường của nó (công khai và riêng tư). Ngược lại, các biến hoặc trường của các loại tham chiếu có thể giữ null hoặc có thể tham chiếu đến một đối tượng, được lưu trữ ở nơi khác, mà bất kỳ số lượng các tham chiếu khác cũng có thể tồn tại. Các trường của một cấu trúc sẽ được lưu trữ ở cùng một nơi với biến hoặc trường của loại cấu trúc đó, có thể nằm trên ngăn xếp hoặc có thể là một phần của đối tượng heap khác.
Tạo một biến hoặc trường của một loại giá trị nguyên thủy sẽ tạo ra nó với một giá trị mặc định; tạo một biến hoặc trường của kiểu cấu trúc sẽ tạo một thể hiện mới, tạo tất cả các trường trong đó theo cách mặc định. Tạo một ví dụ mới của một loại tài liệu tham khảo sẽ bắt đầu bằng cách tạo ra tất cả các lĩnh vực trong đó theo cách mặc định, và sau đó chạy mã bổ sung tùy chọn tùy thuộc vào loại.
Sao chép một biến hoặc trường thuộc kiểu nguyên thủy sang biến khác sẽ sao chép giá trị. Sao chép một biến hoặc trường kiểu cấu trúc sang loại khác sẽ sao chép tất cả các trường (công khai và riêng tư) của thể hiện trước sang thể hiện sau. Sao chép một biến hoặc trường thuộc loại tham chiếu sang biến khác sẽ khiến biến sau tham chiếu cùng thể hiện với biến trước (nếu có).
Điều quan trọng cần lưu ý là trong một số ngôn ngữ như C ++, hành vi ngữ nghĩa của một loại độc lập với cách lưu trữ, nhưng điều đó không đúng với .NET. Nếu một kiểu thực hiện ngữ nghĩa giá trị có thể thay đổi, việc sao chép một biến của loại đó sang một bản sao khác thuộc tính của trường hợp thứ nhất sang trường hợp khác, được gọi bởi lần thứ hai và sử dụng một thành viên của lần thứ hai để thay đổi nó sẽ khiến trường hợp thứ hai bị thay đổi , nhưng không phải là người đầu tiên. Nếu một kiểu thực hiện ngữ nghĩa tham chiếu có thể thay đổi, sao chép một biến này sang một biến khác và sử dụng một thành viên của biến thứ hai để biến đổi đối tượng sẽ ảnh hưởng đến đối tượng được gọi bởi biến thứ nhất; các loại với ngữ nghĩa bất biến không cho phép đột biến, do đó, không quan trọng về mặt ngữ nghĩa cho dù việc sao chép tạo ra một thể hiện mới hay tạo một tham chiếu khác cho lần đầu tiên.
Trong .NET, các loại giá trị có thể thực hiện bất kỳ ngữ nghĩa nào ở trên, với điều kiện là tất cả các trường của chúng có thể thực hiện tương tự. Tuy nhiên, một loại tham chiếu chỉ có thể thực hiện ngữ nghĩa tham chiếu có thể thay đổi hoặc ngữ nghĩa bất biến; các loại giá trị với các trường của các loại tham chiếu có thể thay đổi được giới hạn trong việc triển khai ngữ nghĩa tham chiếu có thể thay đổi hoặc ngữ nghĩa lai kỳ lạ.