Como definir e consumir classes e estruturas (C++/CLI)
Este artigo mostra como definir e consumir tipos de referência definidos pelo usuário e tipos de valor em C++/CLI.
Instanciação de objeto
Os tipos de referência (ref) só podem ser instanciados no heap gerenciado, não na pilha ou no heap nativo. Os tipos de valor podem ser instanciados na pilha ou no heap gerenciado.
// mcppv2_ref_class2.cpp
// compile with: /clr
ref class MyClass {
public:
int i;
// nested class
ref class MyClass2 {
public:
int i;
};
// nested interface
interface struct MyInterface {
void f();
};
};
ref class MyClass2 : public MyClass::MyInterface {
public:
virtual void f() {
System::Console::WriteLine("test");
}
};
public value struct MyStruct {
void f() {
System::Console::WriteLine("test");
}
};
int main() {
// instantiate ref type on garbage-collected heap
MyClass ^ p_MyClass = gcnew MyClass;
p_MyClass -> i = 4;
// instantiate value type on garbage-collected heap
MyStruct ^ p_MyStruct = gcnew MyStruct;
p_MyStruct -> f();
// instantiate value type on the stack
MyStruct p_MyStruct2;
p_MyStruct2.f();
// instantiate nested ref type on garbage-collected heap
MyClass::MyClass2 ^ p_MyClass2 = gcnew MyClass::MyClass2;
p_MyClass2 -> i = 5;
}
Classes implicitamente abstratas
Uma classe implicitamente abstrata não pode ser instanciada. Uma classe é implicitamente abstrata quando:
- o tipo base da classe é uma interface e
- a classe não implementa todas as funções de membro da interface.
Talvez você não consiga construir objetos de uma classe derivada de uma interface. O motivo pode ser que a classe é implicitamente abstrata. Para obter mais informações sobre classes abstratas, confira abstract.
O exemplo de código a seguir demonstra que a classe MyClass
não pode ser instanciada porque a função MyClass::func2
não está implementada. Para habilitar o exemplo a ser compilado, remova a marca de comentário de MyClass::func2
.
// mcppv2_ref_class5.cpp
// compile with: /clr
interface struct MyInterface {
void func1();
void func2();
};
ref class MyClass : public MyInterface {
public:
void func1(){}
// void func2(){}
};
int main() {
MyClass ^ h_MyClass = gcnew MyClass; // C2259
// To resolve, uncomment MyClass::func2.
}
Visibilidade de tipo
Você pode controlar a visibilidade de tipos common language runtime (CLR). Quando o assembly é referenciado, você controla se os tipos no assembly estão visíveis ou não fora do assembly.
public
indica que um tipo é visível para qualquer arquivo de origem que contenha uma diretiva #using
para o assembly que contém o tipo. private
indica que um tipo não é visível para qualquer arquivo de origem que contenha uma diretiva #using
para o assembly que contém o tipo. No entanto, os tipos privados ficam visíveis no mesmo assembly. Por padrão, a visibilidade de uma classe é private
.
Por padrão, antes do Visual Studio 2005, os tipos nativos tinham acessibilidade pública fora do assembly. Habilite o Aviso do Compilador (nível 1) C4692 para ajudá-lo a ver onde os tipos nativos privados são usados incorretamente. Use o pragma make_public para fornecer acessibilidade pública a um tipo nativo em um arquivo de código-fonte que você não pode modificar.
Para obter mais informações, confira Diretiva #using.
O exemplo a seguir mostra como declarar tipos e especificar sua acessibilidade e, em seguida, acessar esses tipos dentro do assembly. Se um assembly que tem tipos privados for referenciado usando #using
, somente os tipos públicos no assembly estarão visíveis.
// type_visibility.cpp
// compile with: /clr
using namespace System;
// public type, visible inside and outside assembly
public ref struct Public_Class {
void Test(){Console::WriteLine("in Public_Class");}
};
// private type, visible inside but not outside assembly
private ref struct Private_Class {
void Test(){Console::WriteLine("in Private_Class");}
};
// default accessibility is private
ref class Private_Class_2 {
public:
void Test(){Console::WriteLine("in Private_Class_2");}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
a->Test();
Private_Class ^ b = gcnew Private_Class;
b->Test();
Private_Class_2 ^ c = gcnew Private_Class_2;
c->Test();
}
Saída
in Public_Class
in Private_Class
in Private_Class_2
Agora, vamos reescrever o exemplo anterior para que ele seja criado como uma DLL.
// type_visibility_2.cpp
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref struct Public_Class {
void Test(){Console::WriteLine("in Public_Class");}
};
// private type, visible inside but not outside the assembly
private ref struct Private_Class {
void Test(){Console::WriteLine("in Private_Class");}
};
// by default, accessibility is private
ref class Private_Class_2 {
public:
void Test(){Console::WriteLine("in Private_Class_2");}
};
O exemplo a seguir mostra como acessar tipos fora do assembly. Neste exemplo, o cliente consome o componente criado no exemplo anterior.
// type_visibility_3.cpp
// compile with: /clr
#using "type_visibility_2.dll"
int main() {
Public_Class ^ a = gcnew Public_Class;
a->Test();
// private types not accessible outside the assembly
// Private_Class ^ b = gcnew Private_Class;
// Private_Class_2 ^ c = gcnew Private_Class_2;
}
Saída
in Public_Class
Visibilidade do membro
Você pode tornar o acesso a um membro de uma classe pública de dentro do mesmo assembly diferente do acesso a ele de fora do assembly usando pares dos especificadores de acesso public
, protected
e private
Essa tabela resume o efeito dos vários especificadores de acesso:
Especificador | Efeito |
---|---|
public |
O membro está acessível dentro e fora do assembly. Para obter mais informações, consulte public . |
private |
O membro é inacessível, dentro e fora do assembly. Para obter mais informações, consulte private . |
protected |
O membro está acessível dentro e fora do assembly, mas apenas para tipos derivados. Para obter mais informações, consulte protected . |
internal |
O membro é público dentro do assembly, mas privado fora do assembly. internal é uma palavra-chave contextual. Saiba mais em Palavras-chave contextuais. |
public protected - ou - protected public |
O membro é público dentro do assembly, mas protegido fora do assembly. |
private protected - ou - protected private |
O membro é protegido dentro do assembly, mas privado fora do assembly. |
O exemplo a seguir mostra um tipo público que tem membros declarados usando os diferentes especificadores de acesso. Em seguida, ele mostra o acesso a esses membros de dentro do assembly.
// compile with: /clr
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
void Public_Function(){System::Console::WriteLine("in Public_Function");}
private:
void Private_Function(){System::Console::WriteLine("in Private_Function");}
protected:
void Protected_Function(){System::Console::WriteLine("in Protected_Function");}
internal:
void Internal_Function(){System::Console::WriteLine("in Internal_Function");}
protected public:
void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}
public protected:
void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}
private protected:
void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}
protected private:
void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Private_Function();
Private_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
MyClass ^ b = gcnew MyClass;
a->Public_Function();
a->Protected_Public_Function();
a->Public_Protected_Function();
// accessible inside but not outside the assembly
a->Internal_Function();
// call protected functions
b->Test();
// not accessible inside or outside the assembly
// a->Private_Function();
}
Saída
in Public_Function
in Protected_Public_Function
in Public_Protected_Function
in Internal_Function
=======================
in function of derived class
in Protected_Function
in Protected_Private_Function
in Private_Protected_Function
exiting function of derived class
=======================
Agora vamos compilar o exemplo anterior como uma DLL.
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
void Public_Function(){System::Console::WriteLine("in Public_Function");}
private:
void Private_Function(){System::Console::WriteLine("in Private_Function");}
protected:
void Protected_Function(){System::Console::WriteLine("in Protected_Function");}
internal:
void Internal_Function(){System::Console::WriteLine("in Internal_Function");}
protected public:
void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}
public protected:
void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}
private protected:
void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}
protected private:
void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Private_Function();
Private_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
O exemplo a seguir consome o componente criado no exemplo anterior. Ele mostra como acessar os membros de fora do assembly.
// compile with: /clr
#using "type_member_visibility_2.dll"
using namespace System;
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
void Test() {
Console::WriteLine("=======================");
Console::WriteLine("in function of derived class");
Protected_Function();
Protected_Public_Function();
Public_Protected_Function();
Console::WriteLine("exiting function of derived class");
Console::WriteLine("=======================");
}
};
int main() {
Public_Class ^ a = gcnew Public_Class;
MyClass ^ b = gcnew MyClass;
a->Public_Function();
// call protected functions
b->Test();
// can't be called outside the assembly
// a->Private_Function();
// a->Internal_Function();
// a->Protected_Private_Function();
// a->Private_Protected_Function();
}
Saída
in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================
Classes nativas públicas e privadas
Um tipo nativo pode ser referenciado de um tipo gerenciado. Por exemplo, uma função em um tipo gerenciado pode usar um parâmetro cujo tipo é um struct nativo. Se o tipo gerenciado e a função forem públicos em um assembly, o tipo nativo também deverá ser público.
// native type
public struct N {
N(){}
int i;
};
Em seguida, crie o arquivo de código-fonte que consome o tipo nativo:
// compile with: /clr /LD
#include "mcppv2_ref_class3.h"
// public managed type
public ref struct R {
// public function that takes a native type
void f(N nn) {}
};
Agora, compile um cliente:
// compile with: /clr
#using "mcppv2_ref_class3.dll"
#include "mcppv2_ref_class3.h"
int main() {
R ^r = gcnew R;
N n;
r->f(n);
}
Construtores estáticos
Um tipo CLR, por exemplo, uma classe ou struct, pode ter um construtor estático que pode ser usado para inicializar membros de dados estáticos. Um construtor estático é chamado no máximo uma vez e é chamado antes que qualquer membro estático do tipo seja acessado pela primeira vez.
Um construtor de instância sempre é executado após um construtor estático.
O compilador não poderá colocar em linha uma chamada para um construtor se a classe tiver um construtor estático. O compilador não poderá inserir uma chamada para qualquer função membro se a classe for um tipo de valor, tiver um construtor estático e não tiver um construtor de instância. O CLR pode embutido na chamada, mas o compilador não pode.
Defina um construtor estático como uma função de membro privado, pois ele deve ser chamado apenas pelo CLR.
Para obter mais informações sobre construtores estáticos, consulte Como definir um construtor estático de interface (C++/CLI).
// compile with: /clr
using namespace System;
ref class MyClass {
private:
static int i = 0;
static MyClass() {
Console::WriteLine("in static constructor");
i = 9;
}
public:
static void Test() {
i++;
Console::WriteLine(i);
}
};
int main() {
MyClass::Test();
MyClass::Test();
}
Saída
in static constructor
10
11
Semântica do ponteiro this
Quando você está usando C++\CLI para definir tipos, o ponteiro this
em um tipo de referência é de identificador de tipo. O ponteiro this
em um tipo de valor é do tipo interior pointer.
Essas diferentes semânticas do ponteiro this
podem causar um comportamento inesperado quando um indexador padrão é chamado. O exemplo a seguir mostra a maneira correta de acessar um indexador padrão em um tipo ref e um tipo de valor.
Para obter mais informações, consulte Operador Handle to Object (^) e interior_ptr (C++/CLI)
// compile with: /clr
using namespace System;
ref struct A {
property Double default[Double] {
Double get(Double data) {
return data*data;
}
}
A() {
// accessing default indexer
Console::WriteLine("{0}", this[3.3]);
}
};
value struct B {
property Double default[Double] {
Double get(Double data) {
return data*data;
}
}
void Test() {
// accessing default indexer
Console::WriteLine("{0}", this->default[3.3]);
}
};
int main() {
A ^ mya = gcnew A();
B ^ myb = gcnew B();
myb->Test();
}
Saída
10.89
10.89
Funções ocultas por assinatura
No C++padrão, uma função em uma classe base fica oculta por uma função que tem o mesmo nome em uma classe derivada, mesmo que a função de classe derivada não tenha o mesmo tipo ou número de parâmetros. É conhecida como semântica hide-by-name. Em um tipo de referência, uma função em uma classe base só será ocultada por uma função em uma classe derivada se o nome e a lista de parâmetros forem os mesmos. É conhecida como semântica hide-by-signature.
Uma classe é considerada uma classe oculta por assinatura quando todas as suas funções são marcadas nos metadados como hidebysig
. Por padrão, todas as classes que foram criadas em /clr
têm hidebysig
funções. Quando uma classe tem hidebysig
funções, o compilador não oculta funções por nome em nenhuma classe base direta, mas se o compilador encontrar uma classe oculta por nome em uma cadeia de herança, ele continuará esse comportamento de ocultar por nome.
Na semântica hide-by-signature, quando uma função é chamada em um objeto, o compilador identifica a classe mais derivada que contém uma função que poderia atender à chamada de função. Se houver apenas uma função na classe que satisfaça a chamada, o compilador chamará essa função. Se houver mais de uma função na classe que possa atender à chamada, o compilador usará regras de resolução de sobrecarga para determinar qual função chamar. Para obter mais informações sobre regras de sobrecarga, consulte Sobrecarga de função.
Para uma determinada chamada de função, uma função em uma classe base pode ter uma assinatura que a torna um pouco melhor do que uma função em uma classe derivada. No entanto, se a função foi explicitamente chamada em um objeto da classe derivada, a função na classe derivada será chamada.
Como o valor retornado não é considerado parte da assinatura de uma função, uma função de classe base fica oculta se tiver o mesmo nome e usar o mesmo tipo e número de argumentos como uma função de classe derivada, mesmo que seja diferente no tipo do valor retornado.
O exemplo a seguir mostra que uma função em uma classe base não está oculta por uma função em uma classe derivada.
// compile with: /clr
using namespace System;
ref struct Base {
void Test() {
Console::WriteLine("Base::Test");
}
};
ref struct Derived : public Base {
void Test(int i) {
Console::WriteLine("Derived::Test");
}
};
int main() {
Derived ^ t = gcnew Derived;
// Test() in the base class will not be hidden
t->Test();
}
Saída
Base::Test
O exemplo a seguir mostra que o compilador do Microsoft C++ chama uma função na classe mais derivada, mesmo que uma conversão seja necessária para corresponder a um ou mais dos parâmetros, e não chame uma função em uma classe base que seja uma correspondência melhor para a chamada de função.
// compile with: /clr
using namespace System;
ref struct Base {
void Test2(Single d) {
Console::WriteLine("Base::Test2");
}
};
ref struct Derived : public Base {
void Test2(Double f) {
Console::WriteLine("Derived::Test2");
}
};
int main() {
Derived ^ t = gcnew Derived;
// Base::Test2 is a better match, but the compiler
// calls a function in the derived class if possible
t->Test2(3.14f);
}
Saída
Derived::Test2
O exemplo a seguir mostra que é possível ocultar uma função mesmo que a classe base tenha a mesma assinatura que a classe derivada.
// compile with: /clr
using namespace System;
ref struct Base {
int Test4() {
Console::WriteLine("Base::Test4");
return 9;
}
};
ref struct Derived : public Base {
char Test4() {
Console::WriteLine("Derived::Test4");
return 'a';
}
};
int main() {
Derived ^ t = gcnew Derived;
// Base::Test4 is hidden
int i = t->Test4();
Console::WriteLine(i);
}
Saída
Derived::Test4
97
Construtores de cópia
O padrão C++ diz que um construtor de cópia é chamado quando um objeto é movido, de modo que um objeto seja criado e destruído no mesmo endereço.
No entanto, quando uma função que é compilada para MSIL chama uma função nativa em que uma classe nativa, ou mais de uma, é passada por valor e onde a classe nativa tem um construtor de cópia ou um destruidor, nenhum construtor de cópia é chamado e o objeto é destruído em um endereço diferente de onde foi criado. Esse comportamento pode causar problemas se a classe tiver um ponteiro para si mesma ou se o código estiver rastreando objetos por endereço.
Para obter mais informações, consulte /clr (compilação de Common Language Runtime).
O exemplo a seguir demonstra quando um construtor de cópia não é gerado.
// compile with: /clr
#include<stdio.h>
struct S {
int i;
static int n;
S() : i(n++) {
printf_s("S object %d being constructed, this=%p\n", i, this);
}
S(S const& rhs) : i(n++) {
printf_s("S object %d being copy constructed from S object "
"%d, this=%p\n", i, rhs.i, this);
}
~S() {
printf_s("S object %d being destroyed, this=%p\n", i, this);
}
};
int S::n = 0;
#pragma managed(push,off)
void f(S s1, S s2) {
printf_s("in function f\n");
}
#pragma managed(pop)
int main() {
S s;
S t;
f(s,t);
}
Saída
S object 0 being constructed, this=0018F378
S object 1 being constructed, this=0018F37C
S object 2 being copy constructed from S object 1, this=0018F380
S object 3 being copy constructed from S object 0, this=0018F384
S object 4 being copy constructed from S object 2, this=0018F2E4
S object 2 being destroyed, this=0018F380
S object 5 being copy constructed from S object 3, this=0018F2E0
S object 3 being destroyed, this=0018F384
in function f
S object 5 being destroyed, this=0018F2E0
S object 4 being destroyed, this=0018F2E4
S object 1 being destroyed, this=0018F37C
S object 0 being destroyed, this=0018F378
Destruidores e finalizadores
Os destruidores em um tipo de referência fazem uma limpeza determinística dos recursos. Os finalizadores limpam recursos não gerenciados e podem ser chamados deterministicamente pelo destruidor ou não deterministicamente pelo coletor de lixo. Para obter informações sobre destruidores no C++padrão, consulte Destruidores.
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
O coletor de lixo CLR exclui objetos gerenciados não utilizados e libera sua memória quando eles não são mais necessários. No entanto, um tipo pode usar recursos que o coletor de lixo não sabe como liberar. Esses recursos são conhecidos como recursos não gerenciados (identificadores de arquivo nativos, por exemplo). Recomendamos que você libere todos os recursos não gerenciados no finalizador. O coletor de lixo libera recursos gerenciados de forma não determinística, portanto, não é seguro fazer referência a recursos gerenciados em um finalizador. Isso porque é possível que o coletor de lixo já os tenha limpo.
Um finalizador do Visual C++ não é o mesmo que o método Finalize. (A documentação do CLR usa o finalizador e o método Finalize sinonimamente). O método Finalize é chamado pelo coletor de lixo, que invoca cada finalizador em uma cadeia de herança de classe. Ao contrário dos destruidores do Visual C++, uma chamada de finalizador de classe derivada não faz com que o compilador invoque o finalizador em todas as classes base.
Como o compilador do Microsoft C++ dá suporte à liberação determinística de recursos, não tente implementar os métodos Dispose ou Finalize. No entanto, se você estiver familiarizado com esses métodos, veja como um finalizador do Visual C++ e um destruidor que chama o mapa do finalizador para o padrão Dispose:
// Visual C++ code
ref class T {
~T() { this->!T(); } // destructor calls finalizer
!T() {} // finalizer
};
// equivalent to the Dispose pattern
void Dispose(bool disposing) {
if (disposing) {
~T();
} else {
!T();
}
}
Um tipo gerenciado também pode usar recursos gerenciados que você prefere liberar deterministicamente. Talvez você não queira que o coletor de lixo libere um objeto não deterministicamente em algum momento depois que o objeto não for mais necessário. A liberação determinística de recursos pode melhorar significativamente o desempenho.
O compilador do Microsoft C++ permite que a definição de um destruidor limpe objetos deterministicamente. Use o destruidor para liberar todos os recursos que você deseja liberar deterministicamente. Se um finalizador estiver presente, chame-o do destruidor para evitar a duplicação de código.
// compile with: /clr /c
ref struct A {
// destructor cleans up all resources
~A() {
// clean up code to release managed resource
// ...
// to avoid code duplication,
// call finalizer to release unmanaged resources
this->!A();
}
// finalizer cleans up unmanaged resources
// destructor or garbage collector will
// clean up managed resources
!A() {
// clean up code to release unmanaged resources
// ...
}
};
Se o código que consome seu tipo não chamar o destruidor, o coletor de lixo acabará liberando todos os recursos gerenciados.
A presença de um destruidor não implica a presença de um finalizador. No entanto, a presença de um finalizador implica que você deve definir um destruidor e chamar o finalizador desse destruidor. Essa chamada fornece a liberação determinística de recursos não gerenciados.
Chamar o destruidor suprime, usando SuppressFinalize, a finalização do objeto. Se o destruidor não for chamado, o finalizador do tipo eventualmente será chamado pelo coletor de lixo.
Você pode melhorar o desempenho chamando o destruidor para limpar deterministicamente os recursos do objeto, em vez de permitir que o CLR finalize o objeto de forma não deterministicamente.
O código escrito no Visual C++ e compilado usando /clr
executa o destruidor de um tipo se:
Um objeto criado usando semântica de pilha sai do escopo. Para obter mais informações, consulte Semântica de Pilha do C++ para tipos de referência.
Uma exceção é gerada durante a construção do objeto.
O objeto é um membro em um objeto cujo destruidor está em execução.
Você chama o operador delete em um identificador (Operador Handle to Object (^)).
Você chama explicitamente o destruidor.
Se um cliente escrito em outro idioma consumir seu tipo, o destruidor será chamado da seguinte maneira:
Em uma chamada para Dispose.
Em uma chamada para
Dispose(void)
no tipo.Se o tipo sair do escopo em uma instrução C#
using
.
Se você não estiver usando semântica de pilha para tipos de referência e criar um objeto de um tipo de referência no heap gerenciado, use a sintaxe try-finally para garantir que uma exceção não impeça a execução do destruidor.
// compile with: /clr
ref struct A {
~A() {}
};
int main() {
A ^ MyA = gcnew A;
try {
// use MyA
}
finally {
delete MyA;
}
}
Se o tipo tiver um destruidor, o compilador gerará um método Dispose
que implementa IDisposable. Se um tipo for escrito no Visual C++ e tiver um destruidor consumido de outra linguagem, chamar IDisposable::Dispose
nesse tipo fará com que o destruidor do tipo seja chamado. Quando o tipo é consumido de um cliente do Visual C++, você não pode chamar Dispose
diretamente; em vez disso, chame o destruidor usando o operador delete
.
Se o tipo tiver um finalizador, o compilador gerará um método Finalize(void)
que substitui Finalize.
Se um tipo tiver um finalizador ou um destruidor, o compilador gerará um método Dispose(bool)
, de acordo com o padrão de design. (Para obter informações, consulte Descartar padrão). Você não pode criar ou chamar Dispose(bool)
explicitamente no Visual C++.
Se um tipo tiver uma classe base que esteja em conformidade com o padrão de design, os destruidores de todas as classes base serão chamados quando o destruidor da classe derivada for chamado. (Se o tipo for escrito no Visual C++, o compilador garantirá que seus tipos implementem esse padrão.) Em outras palavras, o destruidor de uma classe de referência encadeia suas bases e membros, conforme especificado pelo padrão C++. Primeiro, o destruidor da classe é executado. Em seguida, os destruidores de seus membros são executados no inverso da ordem em que foram construídos. Por fim, os destruidores de suas classes base são executados no inverso da ordem em que foram construídos.
Destruidores e finalizadores não são permitidos dentro de tipos de valor ou interfaces.
Um finalizador só pode ser definido ou declarado em um tipo de referência. Como um construtor e um destruidor, um finalizador não tem nenhum tipo de retorno.
Após a execução do finalizador de um objeto, os finalizadores em qualquer classe base também são chamados, começando com o tipo menos derivado. Os finalizadores para membros de dados não são encadeados automaticamente pelo finalizador de uma classe.
Se um finalizador excluir um ponteiro nativo em um tipo gerenciado, você deverá garantir que as referências a ou por meio do ponteiro nativo não sejam coletadas prematuramente. Chame o destruidor no tipo gerenciado em vez de usar KeepAlive.
No tempo de compilação, você pode detectar se um tipo tem um finalizador ou um destruidor. Saiba mais em Suporte para compilador de traços de tipo.
O exemplo a seguir mostra dois tipos: um que tem recursos não gerenciados e outro que tem recursos gerenciados que são liberados deterministicamente.
// compile with: /clr
#include <vcclr.h>
#include <stdio.h>
using namespace System;
using namespace System::IO;
ref class SystemFileWriter {
FileStream ^ file;
array<Byte> ^ arr;
int bufLen;
public:
SystemFileWriter(String ^ name) : file(File::Open(name, FileMode::Append)),
arr(gcnew array<Byte>(1024)) {}
void Flush() {
file->Write(arr, 0, bufLen);
bufLen = 0;
}
~SystemFileWriter() {
Flush();
delete file;
}
};
ref class CRTFileWriter {
FILE * file;
array<Byte> ^ arr;
int bufLen;
static FILE * getFile(String ^ n) {
pin_ptr<const wchar_t> name = PtrToStringChars(n);
FILE * ret = 0;
_wfopen_s(&ret, name, L"ab");
return ret;
}
public:
CRTFileWriter(String ^ name) : file(getFile(name)), arr(gcnew array<Byte>(1024) ) {}
void Flush() {
pin_ptr<Byte> buf = &arr[0];
fwrite(buf, 1, bufLen, file);
bufLen = 0;
}
~CRTFileWriter() {
this->!CRTFileWriter();
}
!CRTFileWriter() {
Flush();
fclose(file);
}
};
int main() {
SystemFileWriter w("systest.txt");
CRTFileWriter ^ w2 = gcnew CRTFileWriter("crttest.txt");
}