Respostas:
No .NET, existem duas categorias de tipos, tipos de referência e tipos de valor .
Estruturas são tipos de valor e classes são tipos de referência .
A diferença geral é que um tipo de referência vive no heap e um tipo de valor vive inline, ou seja, onde quer que sua variável ou campo seja definido.
Uma variável que contém um tipo de valor contém todo o valor do tipo de valor. Para uma estrutura, isso significa que a variável contém toda a estrutura, com todos os seus campos.
Uma variável que contém um tipo de referência contém um ponteiro ou uma referência a outro lugar na memória em que o valor real reside.
Isso tem um benefício, para começar:
Internamente, os tipos de referência são implementados como ponteiros e, sabendo que, e como a atribuição de variáveis funciona, existem outros padrões de comportamento:
Quando você declara variáveis ou campos, veja como os dois tipos diferem:
Um breve resumo de cada um:
Somente aulas:
Somente estruturas:
Classes e estruturas:
c# struct memory overhead
e encontrei essa resposta de Hans Passant que diz que não, esse também não é o caso. Então o que você quer dizer?
class
memória gerenciada (tratadas pelo coletor de lixo), enquanto instâncias de struct
não são .
No .NET, as declarações struct e class diferenciam tipos de referência e tipos de valor.
Quando você passa um tipo de referência, existe apenas um realmente armazenado. Todo o código que acessa a instância está acessando o mesmo.
Quando você passa um tipo de valor, cada um é uma cópia. Todo o código está trabalhando em sua própria cópia.
Isso pode ser mostrado com um exemplo:
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.
Para uma classe isso seria diferente
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.
Classes não podem ser nada - a referência pode apontar para um nulo.
Estruturas são o valor real - elas podem estar vazias, mas nunca nulas. Por esse motivo, as estruturas sempre têm um construtor padrão sem parâmetros - elas precisam de um 'valor inicial'.
Diferença entre estruturas e classes:
Da escolha da Microsoft entre classe e estrutura ...
Como regra geral, a maioria dos tipos em uma estrutura deve ser de classes. Existem, no entanto, algumas situações em que as características de um tipo de valor tornam mais apropriado o uso de estruturas.
✓ considerar um struct em vez de uma classe:
- Se as instâncias do tipo são pequenas e geralmente têm vida curta ou são incorporadas em outros objetos.
X EVITE uma estrutura , a menos que o tipo tenha todas as seguintes características:
- Representa logicamente um valor único, semelhante aos tipos primitivos (int, double, etc.).
- Tem um tamanho de instância com menos de 16 bytes.
- É imutável. (não pode ser mudado)
- Não precisará ser embalado com freqüência.
Além de todas as diferenças descritas nas outras respostas:
Se você estiver após um vídeo explicando todas as diferenças, consulte a Parte 29 - Tutorial em C # - Diferença entre classes e estruturas em C # .
Instâncias de classes são armazenadas no heap gerenciado. Todas as variáveis que contêm uma instância são simplesmente uma referência à instância no heap. Passar um objeto para um método resulta em uma cópia da referência, não no próprio objeto.
Estruturas (tecnicamente, tipos de valor) são armazenadas onde quer que sejam usadas, como um tipo primitivo. O conteúdo pode ser copiado pelo tempo de execução a qualquer momento e sem chamar um construtor de cópias personalizado. Passar um tipo de valor para um método envolve copiar o valor inteiro, novamente sem chamar nenhum código personalizável.
A distinção é aprimorada pelos nomes C ++ / CLI: "ref class" é uma classe conforme descrito primeiro, "value class" é uma classe conforme descrito em segundo. As palavras-chave "class" e "struct", usadas pelo C #, são simplesmente algo que deve ser aprendido.
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
Estrutura vs Classe
Uma estrutura é um tipo de valor, portanto, é armazenada na pilha, mas uma classe é um tipo de referência e é armazenada no heap.
Uma estrutura não suporta herança e polimorfismo, mas uma classe suporta ambos.
Por padrão, todos os membros da estrutura são públicos, mas os membros da classe são por natureza particulares.
Como uma estrutura é um tipo de valor, não podemos atribuir nulo a um objeto struct, mas não é o caso de uma classe.
Para adicionar às outras respostas, há uma diferença fundamental que vale a pena notar, e é assim que os dados são armazenados nas matrizes, pois isso pode ter um efeito importante no desempenho.
Portanto, uma matriz de estruturas se parece com isso na memória
[struct][struct][struct][struct][struct][struct][struct][struct]
Enquanto uma matriz de classes se parece com isso
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
Com uma matriz de classes, os valores nos quais você está interessado não são armazenados na matriz, mas em outros lugares da memória.
Para a grande maioria dos aplicativos, essa diferença realmente não importa, no entanto, no código de alto desempenho, isso afeta a localidade dos dados na memória e tem um grande impacto no desempenho do cache da CPU. O uso de classes quando você poderia / deveria ter usado estruturas aumentaria enormemente o número de falhas de cache na CPU.
A coisa mais lenta que uma CPU moderna faz não é triturar números, é buscar dados da memória e um acerto no cache L1 é muitas vezes mais rápido do que ler dados da RAM.
Aqui está um código que você pode testar. Na minha máquina, a iteração na matriz de classes leva ~ 3x mais tempo que a matriz 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}");
}
Apenas para completá-lo, há outra diferença ao usar o Equals
método, que é herdado por todas as classes e estruturas.
Vamos dizer que temos uma classe e uma estrutura:
class A{
public int a, b;
}
struct B{
public int a, b;
}
e no método Main, temos 4 objetos.
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;
}
Então:
s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false
Portanto , as estruturas são adequadas para objetos numéricos, como pontos (salve as coordenadas xey). E as aulas são adequadas para outros. Mesmo que duas pessoas tenham o mesmo nome, altura, peso ..., ainda são duas pessoas.
Bem, para iniciantes, uma estrutura é passada por valor e não por referência. Estruturas são boas para estruturas de dados relativamente simples, enquanto as classes têm muito mais flexibilidade do ponto de vista arquitetural via polimorfismo e herança.
Outros provavelmente podem lhe dar mais detalhes do que eu, mas uso estruturas quando a estrutura que pretendo é simples.
Além da diferença básica do especificador de acesso, e das poucas mencionadas acima, gostaria de adicionar algumas das principais diferenças, incluindo algumas das mencionadas acima, com uma amostra de código com saída, que fornecerá uma idéia mais clara da referência e do valor
Estruturas:
Classe:
Amostra de código
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;
}
}
Resultado
O valor inicial do objeto Struct é: 10
Método Inside Struct O valor Inside Method do Struct Object é: 20
Após o método, o valor da chamada de Struct Object é: 10
O valor inicial do objeto de classe é: 10
Método Inside Class O valor Inside Method do objeto Class é: 20
Após o método, o valor da chamada de Objeto de Classe é: 20
Aqui você pode ver claramente a diferença entre chamada por valor e chamada por referência.
Os eventos declarados em uma classe têm seu acesso + = e - = bloqueado automaticamente por meio de um bloqueio (isso) para torná-los seguros (os eventos estáticos são bloqueados no tipo da classe). Eventos declarados em uma estrutura não têm seu acesso + = e - = bloqueado automaticamente. Um bloqueio (isso) para uma estrutura não funcionaria, pois você só pode bloquear uma expressão de tipo de referência.
A criação de uma instância struct não pode causar uma coleta de lixo (a menos que o construtor direta ou indiretamente crie uma instância de tipo de referência), enquanto a criação de uma instância do tipo de referência pode causar a coleta de lixo.
Uma estrutura sempre tem um construtor padrão público interno.
class DefaultConstructor
{
static void Eg()
{
Direct yes = new Direct(); // Always compiles OK
InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
//...
}
}
Isso significa que uma estrutura é sempre instanciada, enquanto uma classe pode não ser, pois todos os seus construtores podem ser privados.
class NonInstantiable
{
private NonInstantiable() // OK
{
}
}
struct Direct
{
private Direct() // Compile-time error
{
}
}
Uma estrutura não pode ter um destruidor. Um destruidor é apenas uma substituição de objeto. Finalize disfarçado e estruturas, sendo tipos de valor, não estão sujeitas à coleta de lixo.
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
Uma estrutura está implicitamente selada, uma classe não.
Uma estrutura não pode ser abstrata, uma classe pode.
Uma estrutura não pode chamar: base () em seu construtor, enquanto uma classe sem classe base explícita pode.
Uma estrutura não pode estender outra classe, uma classe pode.
Uma estrutura não pode declarar membros protegidos (por exemplo, campos, tipos aninhados) que uma classe pode.
Uma estrutura não pode declarar membros da função abstrata, uma classe abstrata pode.
Uma estrutura não pode declarar membros da função virtual, uma classe pode.
Uma estrutura não pode declarar membros selados da função, uma classe pode.
Uma estrutura não pode declarar substituir membros da função, uma classe pode.
A única exceção a essa regra é que uma estrutura pode substituir os métodos virtuais de System.Object, viz, Equals () e GetHashCode () e ToString ().
Object
, que conteria uma referência a uma cópia em caixa da estrutura.
Como mencionado anteriormente: Classes são do tipo referência, enquanto Structs são do tipo valor, com todas as consequências.
Como regra geral, o Framework Design Guidelines recomenda o uso de Structs em vez de classes se:
Há um caso interessante do quebra-cabeça "classe versus estrutura" - situação em que você precisa retornar vários resultados do método: escolha qual usar. Se você conhece a história ValueTuple - você sabe que ValueTuple (struct) foi adicionado porque deveria ser mais eficaz que Tuple (classe). Mas o que isso significa em números? Dois testes: um é struct / class que possui 2 campos, outro com struct / class que possui 8 campos (com dimensão maior que 4 - classe deve se tornar mais eficaz que estrutura em termos de ticks de processador, mas é claro que a carga do GC também deve ser considerada )
PS Existe outra referência para o caso específico 'sturct ou classe com coleções': 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 |
Teste de código:
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;
}
}
}
}
Estruturas são o valor real - elas podem estar vazias, mas nunca nulas
Isso é verdade, no entanto, observe também que as estruturas do .NET 2 oferecem suporte a uma versão Nullable e o C # fornece algum açúcar sintático para facilitar o uso.
int? value = null;
value = 1;
(object)(default(int?)) == null
que você não pode fazer com qualquer outro tipo de valor, porque há mais do que apenas açúcar acontecendo aqui. O único açúcar é int?
para Nullable<int>
.
Toda variável ou campo de um tipo de valor primitivo ou tipo de estrutura mantém uma instância exclusiva desse tipo, incluindo todos os seus campos (público e privado). Por outro lado, variáveis ou campos de tipos de referência podem ser nulos ou podem se referir a um objeto armazenado em outro local, ao qual também pode existir qualquer número de outras referências. Os campos de uma estrutura serão armazenados no mesmo local que a variável ou o campo desse tipo de estrutura, que pode estar na pilha ou fazer parte de outro objeto de heap.
Criar uma variável ou campo de um tipo de valor primitivo o criará com um valor padrão; criar uma variável ou campo de um tipo de estrutura criará uma nova instância, criando todos os campos nela da maneira padrão. Criando uma nova instância de um tipo de referência começará criando todos os campos da maneira padrão e executando o código adicional opcional, dependendo do tipo.
Copiar uma variável ou campo de um tipo primitivo para outro copiará o valor. A cópia de uma variável ou campo do tipo de estrutura para outra copiará todos os campos (públicos e privados) da instância anterior para a última instância. Copiar uma variável ou campo do tipo de referência para outro fará com que o último se refira à mesma instância que o primeiro (se houver).
É importante observar que em algumas linguagens como C ++, o comportamento semântico de um tipo é independente de como é armazenado, mas isso não ocorre no .NET. Se um tipo implementa semântica de valor mutável, a cópia de uma variável desse tipo para outra copia as propriedades da primeira para outra instância, mencionada pela segunda, e o uso de um membro da segunda para fazer a mutação fará com que a segunda instância seja alterada. , mas não o primeiro. Se um tipo implementa semântica de referência mutável, copiar uma variável para outra e usar um membro do segundo para alterar o objeto afetará o objeto referido pela primeira variável; tipos com semântica imutável não permitem mutação; portanto, não importa semanticamente se a cópia cria uma nova instância ou cria outra referência à primeira.
No .NET, é possível que os tipos de valor implementem qualquer uma das semânticas acima, desde que todos os seus campos possam fazer o mesmo. Um tipo de referência, no entanto, só pode implementar semântica de referência mutável ou semântica imutável; tipos de valor com campos de tipos de referência mutáveis são limitados à implementação de semântica de referência mutável ou semântica híbrida estranha.