Qual a diferença entre struct e classe no .NET?

Respostas:

1058

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:

  • tipos de valor sempre contêm um valor
  • tipos de referência podem conter uma referência nula , o que significa que eles não se referem a nada no momento

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:

  • copiar o conteúdo de uma variável de tipo de valor para outra variável, copia todo o conteúdo para a nova variável, tornando as duas distintas. Em outras palavras, após a cópia, as alterações em uma não afetam a outra
  • copiar o conteúdo de uma variável de tipo de referência para outra variável, copia a referência, o que significa que agora você tem duas referências à mesma em outro lugar para armazenamento dos dados reais. Em outras palavras, após a cópia, alterar os dados em uma referência também afetará a outra, mas apenas porque você realmente está apenas olhando para os mesmos dados nos dois lugares

Quando você declara variáveis ​​ou campos, veja como os dois tipos diferem:

  • variável: o tipo de valor vive na pilha, o tipo de referência vive na pilha como um ponteiro para algum lugar na memória de pilha onde a memória real vive (embora observe a série de artigos de Eric Lipperts: A pilha é um detalhe de implementação .)
  • classe / estrutura-campo: o tipo de valor vive completamente dentro do tipo, o tipo de referência vive dentro do tipo como um ponteiro para algum lugar na memória de pilha onde a memória real vive.
Lasse V. Karlsen
fonte
43
No interesse da integralidade, devo mencionar que Eric Lippert disse que a pilha é um detalhe de implementação , sempre que mencionei a pilha acima, tenha em mente as postagens de Eric.
Lasse V. Karlsen
2
Isso também é válido para C ++?
Koray Tugay
9
Outra diferença crucial é o uso. No MSDN: "estruturas são normalmente usadas para encapsular um pequeno grupo de variáveis ​​relacionadas, como coordenadas de retângulo. As estruturas também podem conter construtores, constantes, campos, métodos, propriedades, indexadores, operadores, eventos e tipos aninhados, embora se vários membros são obrigatórios, considere fazer do seu tipo uma classe. "
thewpfguy 27/02
4
@KorayTugay Não, não é.
ZoomIn
9
@KorayTugay em C ++ struct e classe são absolutamente equivalentes, exceto para a uma coisa - restrição de acesso padrão (classe tem privados por padrão, struct tem público)
Berkus
207

Um breve resumo de cada um:

Somente aulas:

  • Pode suportar herança
  • São tipos de referência (ponteiro)
  • A referência pode ser nula
  • Ter sobrecarga de memória por nova instância

Somente estruturas:

  • Não pode suportar herança
  • São tipos de valor
  • São passados ​​por valor (como números inteiros)
  • Não pode ter uma referência nula (a menos que Nullable seja usado)
  • Não possui sobrecarga de memória por nova instância - a menos que seja 'in a box'

Classes e estruturas:

  • Os tipos de dados compostos são normalmente usados ​​para conter algumas variáveis ​​que possuem algum relacionamento lógico
  • Pode conter métodos e eventos
  • Pode suportar interfaces
Thomas Bratt
fonte
16
Há algumas partes desta resposta que não estão certas. As classes nem sempre vão para a pilha e as estruturas nem sempre vão para a pilha. As exceções atuais incluem campos struct em uma classe, variáveis ​​capturadas em métodos anônimos e expressões lambda, blocos de iteradores e os valores em caixa já mencionados. Mas a alocação de pilha versus pilha é um detalhe da implementação e pode estar sujeita a alterações. Eric lippart discute isso aqui . Eu diminuí o voto, mas felizmente o removerei se você atualizar.
Simon P Stevens
1
struct não suporta herança de outras estruturas / classes, mas você PODE implementar uma interface em uma estrutura.
thewpfguy
2
Convém esclarecer o que você quer dizer quando afirma que as estruturas "Não têm sobrecarga de memória por nova instância" . Minha primeira interpretação foi que você estava reivindicando - obviamente absurdamente - que estruturas usam memória zero. Então pensei que talvez você esteja tentando dizer que uma estrutura, diferente de uma classe, requer exatamente tanta memória quanto a soma de seus campos membros, e não mais. Mas então eu pesquisei c# struct memory overheade 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?
Mark # Amery #
4
@MarkAmery Tive a mesma reação inicial que você id para a expressão "sem sobrecarga de memória", mas acho que o OP está se referindo ao fato de que instâncias de classmemória gerenciada (tratadas pelo coletor de lixo), enquanto instâncias de structnão são .
Hutch
1
"Struct São passados ​​por valor (como números inteiros)" é falso: todas as variáveis ​​são passadas por valor, também o tipo de referência. Se você deseja passar uma variável por referência, use a palavra-chave "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli 8/17
41

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'.

Keith
fonte
@ T.Todua Sim, existem respostas melhores acima, que votei e escolhi como a resposta depois de fornecer esta - essa é a versão beta inicial do SO, quando ainda estávamos descobrindo as regras.
Keith
1
Eu não sei se você me entendeu corretamente, eu realmente votei / aceitei sua resposta (em oposição às respostas acima), porque você teve bons exemplos (não apenas explicação teórica, em oposição à resposta acima, que tinha apenas explicações teóricas sem exemplos )
21419 TJP
24

Diferença entre estruturas e classes:

  • Estruturas são do tipo valor, enquanto Classes são do tipo referência .
  • As estruturas são armazenadas na pilha, enquanto as Classes são armazenadas na pilha .
  • Os tipos de valor mantêm seu valor na memória onde são declarados, mas o tipo de referência mantém uma referência a uma memória de objeto.
  • Os tipos de valor destruídos imediatamente após a perda do escopo, enquanto o tipo de referência apenas a variável destrói após a perda do escopo. O objeto é posteriormente destruído pelo coletor de lixo.
  • Quando você copia struct em outra estrutura, uma nova cópia dessa estrutura é criada modificada de uma estrutura não afeta o valor da outra estrutura.
  • Quando você copia uma classe para outra, ela copia apenas a variável de referência.
  • A variável de referência aponta para o mesmo objeto no heap. Mudar para uma variável afetará a outra variável de referência.
  • Estruturas não podem ter destruidores , mas classes podem ter destruidores.
  • Estruturas não podem ter construtores explícitos sem parâmetros, enquanto uma classe pode não suportar herança, mas classes sim. Ambos suportam herança de uma interface.
  • As estruturas são do tipo selado .
shana
fonte
21

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.
Sunsetquest
fonte
19

Além de todas as diferenças descritas nas outras respostas:

  1. Estruturas não podem ter um construtor explícito sem parâmetros, enquanto uma classe pode
  2. Estruturas não podem ter destruidores , enquanto uma classe pode
  3. Estruturas não podem herdar de outra estrutura ou classe, enquanto uma classe pode herdar de outra classe. (Estruturas e classes podem ser implementadas a partir de uma interface.)

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 # .

Venkat
fonte
4
Muito mais significativo do que o fato de que as linguagens .net geralmente não permitem que uma estrutura defina um construtor sem parâmetros (a decisão de permitir ou não que seja feita pelo compilador de linguagem) é o fato de que estruturas podem surgir e serem expostas para o mundo externo sem que nenhum tipo de construtor tenha sido executado (mesmo quando um construtor sem parâmetros é definido). A razão pela qual as linguagens .net geralmente proíbem construtores sem parâmetros para estruturas é evitar a confusão que resultaria em ter esses construtores às vezes executados e outras não.
Supercat 15/06
15

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.

Zooba
fonte
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                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
0xaryan
fonte
1
Isso é realmente esplêndido: resumido e informativo. Lembre-se de revisar sua resposta pelo menos uma vez - você trocou as explicações de struct e de classe em algumas linhas; também existem alguns erros de digitação.
Robert Synoradzki 5/11
1
@ensisNoctis Desculpe por esses erros e obrigado pela edição. I deve voltar a ler as minhas respostas 😅
0xaryan
8

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.

Swagatika dhal
fonte
5
Em relação a "todos os membros da estrutura são públicos": Se não me engano, isso está incorreto. "O nível de acesso para membros da classe e membros da estrutura, incluindo classes e estruturas aninhadas, é privado por padrão." msdn.microsoft.com/pt-br/library/ms173121.aspx
Nate Cook
8

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.

  • Com uma estrutura, a matriz contém a instância da estrutura
  • Com uma classe, a matriz contém um ponteiro para uma instância da classe em outro lugar na memória

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}");
    }
Will Calderwood
fonte
-1; "Estruturas são tipos de valor; portanto, armazenam um valor; as classes são tipos de referência; portanto, fazem referência a uma classe." não é claro e improvável que faça sentido para quem ainda não o entendeu pelas outras respostas aqui e "Com uma classe, a classe que contém apenas conterá um ponteiro para a nova classe em uma área diferente da memória". confunde classes com instâncias de classe.
Mark Amery
@ MarkAmery Eu tentei esclarecer um pouco. O ponto que eu realmente estava tentando destacar era a diferença na maneira como as matrizes funcionam com tipos de valor e referência e o efeito que isso tem no desempenho. Eu não estava tentando explicar novamente qual é o valor e os tipos de referência, pois isso é feito em muitas outras respostas.
Will Calderwood,
7

Apenas para completá-lo, há outra diferença ao usar o Equalsmé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.

Ning
fonte
6

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.

Ed S.
fonte
4

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:

  • São tipos de valor e não requerem alocação de heap.
  • A alocação de memória é diferente e é armazenada na pilha
  • Útil para pequenas estruturas de dados
  • Afeta o desempenho, quando passamos valor ao método, passamos toda a estrutura de dados e tudo é passado para a pilha.
  • O construtor simplesmente retorna o próprio valor da estrutura (normalmente em um local temporário na pilha) e esse valor é copiado conforme necessário
  • Cada uma das variáveis ​​possui sua própria cópia dos dados e não é possível que operações em uma afetem a outra.
  • Não oferecem suporte à herança especificada pelo usuário e eles herdam implicitamente do objeto de tipo

Classe:

  • Valor do tipo de referência
  • Armazenado em Heap
  • Armazenar uma referência a um objeto alocado dinamicamente
  • Os construtores são chamados com o novo operador, mas isso não aloca memória no heap
  • Várias variáveis ​​podem ter uma referência ao mesmo objeto
  • É possível que operações em uma variável afetem o objeto referenciado pela outra variável

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.

Arijit Mukherjee
fonte
4
  1. 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.

  2. 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.

  3. 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
        {
        }
    }
  4. 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
  5. 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 ().

Zain Ali
fonte
Em que circunstâncias alguém usaria um evento com uma estrutura? Eu posso imaginar que um programa escrito com muito cuidado possa usar eventos com uma estrutura de uma maneira que funcione, mas somente se a estrutura nunca tiver sido copiada ou transmitida por valor; nesse caso, também poderá ser uma classe.
Super12 /
@supercat Sim, um evento não estático em uma estrutura seria muito estranho, e será útil apenas para estruturas mutáveis, e o próprio evento (se for um evento "do tipo campo") transforma a estrutura na "mutável "e também introduz um campo do tipo de referência na estrutura. Eventos não estáticos em estruturas devem ser maus.
Jeppe Stig Nielsen
@JeppeStigNielsen: O único padrão que eu podia ver onde faria sentido uma estrutura ter um evento seria se o objetivo da estrutura fosse manter uma referência imutável a um objeto de classe para o qual ele se comportasse como proxy. Os eventos automáticos seriam totalmente inúteis nesse cenário; em vez disso, os eventos de inscrição e cancelamento de assinatura teriam que ser retransmitidos para a classe por trás da estrutura. Eu gostaria que o .NET tivesse (ou tornaria possível definir) um tipo de estrutura 'cache-box "com um campo oculto do tipo inicialmente nulo Object, que conteria uma referência a uma cópia em caixa da estrutura.
supercat
1
@JeppeStigNielsen: Estruturas superam as classes em muitos cenários de uso de proxy; o maior problema com o uso de estruturas é que, nos casos em que o boxe acaba sendo necessário, muitas vezes acaba sendo adiado para um loop interno. Se houvesse uma maneira de evitar que as estruturas fossem encaixotadas repetidamente , elas seriam melhores que as classes em muitos outros cenários de uso.
precisa
4

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:

  • Tem um tamanho de instância com menos de 16 bytes
  • Representa logicamente um valor único, semelhante aos tipos primitivos (int, double, etc.)
  • É imutável
  • Não terá que ser encaixotado frequentemente
kb9
fonte
3

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;
            }
        }
    }
}
Roman Pokrovskij
fonte
2

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;
Denis Phillips
fonte
1
Esteja ciente de que este é apenas o açúcar sintático que lê 'Nullable <int> value = null;'
Erik van Brakel 4/08
@ErikvanBrakel Isso não é apenas açúcar sintático. As diferentes regras de boxe significam o (object)(default(int?)) == nullque 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>.
Jon Hanna
-1; isso não aborda a questão de qual é a diferença entre estruturas e classes e, como tal, deveria ter sido um comentário sobre a resposta que você está respondendo, não uma resposta separada. (Embora talvez as normas do site estavam de volta diferente em agosto de 2008!)
Mark Amery
1

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.

supercat
fonte