Odpowiedzi:
W .NET istnieją dwie kategorie typów, typy referencyjne i typy wartości .
Struktury są typami wartości, a klasy są typami referencyjnymi .
Ogólna różnica polega na tym, że typ odniesienia znajduje się na stercie, a typ wartości - w wierszu, to znaczy tam, gdzie jest zdefiniowana zmienna lub pole.
Zmienna zawierająca typ wartości zawiera całą wartość typu wartości. W przypadku struct oznacza to, że zmienna zawiera całą strukturę wraz ze wszystkimi jej polami.
Zmienna zawierająca typ odwołania zawiera wskaźnik lub odwołanie do innego miejsca w pamięci, w którym znajduje się rzeczywista wartość.
Ma to jedną zaletę, na początek:
Wewnętrznie typy referencyjne są implementowane jako wskaźniki, a wiedząc, że i wiedząc, jak działa przypisywanie zmiennych, istnieją inne wzorce zachowań:
Kiedy deklarujesz zmienne lub pola, oto jak różnią się dwa typy:
Krótkie podsumowanie każdego:
Tylko klasy:
Tylko konstrukcje:
Zarówno klasy, jak i struktury:
c# struct memory overhead
i znalazłem odpowiedź Hansa Passanta, która mówi, że nie, to też nie jest przypadek. Więc co ma pan na myśli?
class
zarządzana jest pamięć (obsługiwana przez moduł odśmiecający), podczas gdy instancje struct
nie są .
W .NET deklaracje struct i class rozróżniają typy referencyjne i typy wartości.
Kiedy omijasz typ referencyjny, jest tylko jeden faktycznie zapisany. Cały kod, który uzyskuje dostęp do instancji, ma dostęp do tego samego.
Gdy podasz typ wartości, każdy z nich jest kopią. Cały kod działa na własnej kopii.
Można to pokazać na przykładzie:
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.
Dla klasy byłoby inaczej
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.
Klasy mogą być niczym - odwołanie może wskazywać na zero.
Struktury są rzeczywistą wartością - mogą być puste, ale nigdy zerowe. Z tego powodu struktury zawsze mają domyślny konstruktor bez parametrów - potrzebują „wartości początkowej”.
Różnica między strukturami i klasami:
Od wyboru między klasą a strukturą Microsoftu ...
Z reguły większość typów w ramach powinna stanowić klasy. Istnieją jednak pewne sytuacje, w których właściwości typu wartości sprawiają, że bardziej odpowiednie jest użycie struktur.
✓ UWAŻAJ strukturę zamiast klasy:
- Jeśli wystąpienia tego typu są małe i zwykle krótkotrwałe lub są zwykle osadzone w innych obiektach.
X UNIKAĆ struktury, chyba że typ ma wszystkie następujące cechy:
- Logicznie reprezentuje pojedynczą wartość, podobną do typów pierwotnych (int, double itp.).
- Ma rozmiar instancji poniżej 16 bajtów.
- Jest niezmienny. (nie może zostać zmieniony)
- Nie będzie trzeba go często zapakować.
Oprócz wszystkich różnic opisanych w innych odpowiedziach:
Jeśli szukasz filmu wyjaśniającego wszystkie różnice, możesz zapoznać się z częścią 29 - Kurs C # - Różnica między klasami a strukturami w języku C # .
Wystąpienia klas są przechowywane na zarządzanej stercie. Wszystkie zmienne „zawierające” instancję są po prostu odwołaniem do instancji na stercie. Przekazanie obiektu do metody powoduje przekazanie kopii odwołania, a nie samego obiektu.
Struktury (technicznie, typy wartości) są przechowywane wszędzie tam, gdzie są używane, podobnie jak typ pierwotny. Zawartość może być kopiowana przez środowisko wykonawcze w dowolnym momencie i bez wywoływania dostosowanego konstruktora kopii. Przekazanie typu wartości do metody wymaga skopiowania całej wartości, ponownie bez wywoływania dostosowywanego kodu.
Rozróżnienie jest poprawione przez nazwy C ++ / CLI: „klasa referencyjna” jest klasą, jak opisano najpierw, „klasa wartości” jest klasą opisaną jako druga. Słowa kluczowe „class” i „struct” używane w języku C # są po prostu czymś, czego należy się nauczyć.
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
Struktura a klasa
Struktura jest typem wartości, więc jest przechowywana na stosie, ale klasa jest typem referencyjnym i jest przechowywana na stercie.
Struktura nie obsługuje dziedziczenia i polimorfizmu, ale klasa obsługuje oba te elementy.
Domyślnie wszyscy członkowie struktury są publiczni, ale członkowie klasy mają z natury charakter prywatny.
Ponieważ struktura jest typem wartości, nie możemy przypisać wartości null do obiektu struct, ale nie jest tak w przypadku klasy.
Aby dodać do innych odpowiedzi, należy zwrócić uwagę na jedną zasadniczą różnicę, a mianowicie sposób przechowywania danych w tablicach, ponieważ może to mieć duży wpływ na wydajność.
Tak więc tablica struktur wygląda tak w pamięci
[struct][struct][struct][struct][struct][struct][struct][struct]
Natomiast tablica klas wygląda tak
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
W tablicy klas wartości, które Cię interesują, nie są przechowywane w tablicy, ale w innej pamięci.
W zdecydowanej większości aplikacji różnica ta nie ma tak naprawdę znaczenia, jednak w kodzie o wysokiej wydajności wpłynie to na lokalizację danych w pamięci i będzie miało duży wpływ na wydajność pamięci podręcznej procesora. Używanie klas, gdy można / należy użyć struktur, znacznie zwiększy liczbę braków pamięci podręcznej na procesorze.
Najwolniejszą rzeczą, jaką robi współczesny procesor, nie jest niszczenie liczb, pobieranie danych z pamięci, a trafienie w pamięć podręczną L1 jest wielokrotnie szybsze niż odczyt danych z pamięci RAM.
Oto kod, który możesz przetestować. Na moim komputerze iteracja po tablicy klas zajmuje ~ 3 razy dłużej niż tablica 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}");
}
Aby go ukończyć, istnieje inna różnica w korzystaniu z Equals
metody, która jest dziedziczona przez wszystkie klasy i struktury.
Powiedzmy, że mamy klasę i strukturę:
class A{
public int a, b;
}
struct B{
public int a, b;
}
a w metodzie Main mamy 4 obiekty.
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;
}
Następnie:
s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false
Tak więc struktury są odpowiednie dla obiektów numerycznych, takich jak punkty (zapisz współrzędne xiy). A zajęcia są odpowiednie dla innych. Nawet jeśli 2 osoby mają to samo imię, wzrost, wagę ... to nadal są 2 osoby.
Cóż, na początek, struktura jest przekazywana raczej przez wartość niż przez referencję. Struktury są dobre dla stosunkowo prostych struktur danych, podczas gdy klasy mają znacznie większą elastyczność z architektonicznego punktu widzenia dzięki polimorfizmowi i dziedziczeniu.
Inni prawdopodobnie zapewnią ci więcej szczegółów niż ja, ale używam struktur, gdy struktura, do której dążę, jest prosta.
Oprócz podstawowej różnicy między specyfikatorem dostępu i kilkoma wymienionymi powyżej, chciałbym dodać niektóre z głównych różnic, w tym kilka wspomnianych powyżej, z próbką kodu z wyjściem, która da wyraźniejszy obraz odniesienia i wartości
Struktury:
Klasa:
Próbka kodu
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;
}
}
Wynik
Początkowa wartość obiektu konstrukcyjnego to: 10
Metoda konstrukcji wewnętrznej Wartość metody wewnętrznej obiektu konstrukcyjnego wynosi: 20
Po wywołaniu metody wartość obiektu konstrukcyjnego wynosi: 10
Początkowa wartość obiektu klasy to: 10
Metoda klasy wewnętrznej Wartość metody wewnętrznej obiektu klasy wynosi: 20
Po wywołaniu metody wartość obiektu klasy wynosi: 20
Tutaj możesz wyraźnie zobaczyć różnicę między połączeniem według wartości a wywołaniem przez odniesienie.
Zdarzenia zadeklarowane w klasie mają swój dostęp + = i - = automatycznie blokowane przez blokadę (this), aby były bezpieczne dla wątków (zdarzenia statyczne są blokowane na typie klasy). Zdarzenia zadeklarowane w strukturze nie mają automatycznie blokowanego dostępu + = i - =. Blokada (this) dla struktury nie działałaby, ponieważ można zablokować tylko wyrażenie typu referencyjnego.
Utworzenie instancji struct nie może spowodować wyrzucania elementów bezużytecznych (chyba że konstruktor bezpośrednio lub pośrednio utworzy instancję typu odwołania), natomiast utworzenie instancji typu odniesienia może spowodować odśmiecanie.
Struktura zawsze ma wbudowany publiczny konstruktor domyślny.
class DefaultConstructor
{
static void Eg()
{
Direct yes = new Direct(); // Always compiles OK
InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
//...
}
}
Oznacza to, że struktura jest zawsze możliwa do utworzenia, podczas gdy klasa może nie być, ponieważ wszystkie jej konstruktory mogą być prywatne.
class NonInstantiable
{
private NonInstantiable() // OK
{
}
}
struct Direct
{
private Direct() // Compile-time error
{
}
}
Struktura nie może mieć destruktora. Destruktor to po prostu przesłonięcie obiektu. Finalizacja w przebraniu, a struktury, będące typami wartości, nie podlegają odśmiecaniu.
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
Struktura jest domyślnie zapieczętowana, klasa nie.
Struktura nie może być abstrakcyjna, klasa może.
Struktur nie może wywołać: base () w swoim konstruktorze, podczas gdy klasa bez wyraźnej klasy bazowej może.
Struktura nie może rozszerzyć innej klasy, klasa może.
Struktura nie może deklarować chronionych elementów (na przykład pól, typów zagnieżdżonych), które klasa może.
Struktura nie może deklarować elementów funkcji abstrakcyjnych, klasa abstrakcyjna może.
Struktura nie może deklarować elementów funkcji wirtualnej, klasa może.
Struktura nie może deklarować zapieczętowanych elementów funkcji, klasa może.
Struktura nie może zadeklarować elementów funkcji zastępowania, klasa może to zrobić.
Jedynym wyjątkiem od tej reguły jest to, że struct może zastąpić wirtualne metody System.Object, viz, Equals () i GetHashCode () oraz ToString ().
Object
, które zawierałoby odniesienie do pudełkowej kopii struktury.
Jak wspomniano wcześniej: klasy są typem odniesienia, natomiast struktury są typami wartości ze wszystkimi konsekwencjami.
Zasadniczo Wytyczne projektowania ramowego zalecają stosowanie Struktur zamiast klas, jeśli:
Jest jeden interesujący przypadek układanki „klasa kontra struktura” - sytuacja, w której musisz zwrócić kilka wyników z metody: wybierz, której użyć. Jeśli znasz historię ValueTuple - wiesz, że dodano ValueTuple (struct), ponieważ powinna być bardziej skuteczna niż Tuple (klasa). Ale co to oznacza w liczbach? Dwa testy: jeden to struct / klasa, która ma 2 pola, drugi z struct / class, które mają 8 pól (o wymiarze większym niż 4 - klasa powinna stać się bardziej efektywna niż struct pod względem tików procesora, ale oczywiście należy również wziąć pod uwagę obciążenie GC ).
PS Kolejnym punktem odniesienia dla konkretnego przypadku „solidna lub klasa ze zbiorami” jest: 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 |
Test kodu:
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;
}
}
}
}
Struktury są rzeczywistą wartością - mogą być puste, ale nigdy zerowe
To prawda, należy jednak pamiętać, że od .NET 2 struktury obsługują wersję Nullable, a C # dostarcza trochę cukru syntaktycznego, aby było łatwiejsze w użyciu.
int? value = null;
value = 1;
(object)(default(int?)) == null
że nie możesz zrobić z żadnym innym rodzajem wartości, ponieważ dzieje się tutaj coś więcej niż tylko cukier. Jedyny cukier jest int?
dla Nullable<int>
.
Każda zmienna lub pole pierwotnego typu wartości lub typu struktury zawiera unikalną instancję tego typu, w tym wszystkie jego pola (publiczne i prywatne). Natomiast zmienne lub pola typów odwołań mogą mieć wartość null lub mogą odnosić się do obiektu przechowywanego w innym miejscu, do którego może istnieć dowolna liczba innych odwołań. Pola struktury będą przechowywane w tym samym miejscu, co zmienna lub pole tego typu struktury, które mogą znajdować się na stosie lub mogą być częścią innego obiektu stosu.
Utworzenie zmiennej lub pola o pierwotnym typie wartości spowoduje utworzenie go z wartością domyślną; utworzenie zmiennej lub pola typu struktury spowoduje utworzenie nowej instancji, tworząc wszystkie pola w niej w sposób domyślny. Utworzenie nowej instancji typu odwołania rozpocznie się od utworzenia wszystkich pól w niej w sposób domyślny, a następnie uruchomienia opcjonalnego dodatkowego kodu w zależności od typu.
Skopiowanie jednej zmiennej lub pola typu pierwotnego do innej spowoduje skopiowanie wartości. Skopiowanie jednej zmiennej lub pola typu struktury do drugiej spowoduje skopiowanie wszystkich pól (publicznych i prywatnych) z pierwszej instancji do drugiej. Skopiowanie jednej zmiennej lub pola typu odniesienia do innego spowoduje, że ta ostatnia będzie odnosić się do tej samej instancji co pierwsza (jeśli istnieje).
Należy zauważyć, że w niektórych językach, takich jak C ++, zachowanie semantyczne typu jest niezależne od sposobu jego przechowywania, ale nie jest to prawdą w przypadku .NET. Jeśli typ implementuje semantykę wartości zmiennej, kopiowanie jednej zmiennej tego typu do innej kopiuje właściwości pierwszej do innej instancji, do której odwołuje się druga, i użycie elementu drugiej mutacji spowoduje zmianę tej drugiej instancji , ale nie pierwszy. Jeśli typ implementuje zmienną semantykę odniesienia, skopiowanie jednej zmiennej do drugiej i użycie elementu drugiej do mutacji obiektu wpłynie na obiekt, do którego odnosi się pierwsza zmienna; typy z niezmienną semantyką nie pozwalają na mutację, więc nie ma znaczenia semantycznego, czy kopiowanie tworzy nową instancję, czy tworzy kolejne odwołanie do pierwszej.
W .NET typy wartości mogą implementować dowolną z powyższych semantyek, pod warunkiem, że wszystkie ich pola mogą zrobić podobnie. Typ referencyjny może jednak implementować tylko semantykę zmienną referencyjną lub semantykę niezmienną; typy wartości z polami zmiennych typów zmiennych są ograniczone do implementacji semantyki zmiennych zmiennych lub dziwnej hybrydowej semantyki.