
Vamos ver o que o blog diz sobre esta mudança no C # 8.0 (versão Visual Studio 2019 Preview 2):
“Estruturas somente de pilha apareceram no C # 7.2. Eles são extremamente úteis, mas ao mesmo tempo seu uso está intimamente relacionado a restrições, por exemplo, a incapacidade de implementar interfaces. Agora, as estruturas de link podem ser limpas usando o método Dispose dentro delas, sem usar a interface IDisposable ".
É o que acontece: as estruturas ref somente de pilha não implementam interfaces, caso contrário, a probabilidade de sua embalagem surgiria. Portanto, eles não podem implementar IDisposable e não podemos usar essas estruturas na instrução using:
class Program { static void Main(string[] args) { using (var book = new Book()) { Console.WriteLine("Hello World!"); } } } ref struct Book : IDisposable { public void Dispose() { } }
Tentar executar esse código resultará em um erro de compilação :
Error CS8343 'Book': ref structs cannot implement interfaces
No entanto, agora se adicionarmos o método público Dispose
à estrutura de referência, a instrução using
o aceitará magicamente e tudo será compilado:
class Program { static void Main(string[] args) { using (var book = new Book()) { // ... } } } ref struct Book { public void Dispose() { } }
Além disso, graças às alterações na própria declaração, agora você pode usar usando uma forma mais curta (as chamadas declarações using
):
class Program { static void Main(string[] args) { using var book = new Book(); // ... } }
Mas ... porque?
Essa é uma longa história, mas, em geral, a limpeza explícita (finalização determinística) é preferível à implícita (finalização não determinística). Isso é intuitivo. É melhor limpar explicitamente os recursos o mais rápido possível (chamando Close, Dispose ou usando a instrução), em vez de esperar pela limpeza implícita que ocorrerá "algum dia" (quando o próprio ambiente iniciar os finalizadores).
Portanto, ao criar um tipo que possui um determinado recurso, é melhor prever a possibilidade de limpeza explícita. Em C #, essa é obviamente a interface IDisposable
e seu método Dispose
.
Nota Não esqueça que, no caso de estruturas de referência, somente a limpeza explícita é usada, pois a definição de finalizadores para eles é impossível.
Vejamos um exemplo ilustrativo do "invólucro usual para um conjunto de memórias não gerenciadas". Ocupa o menor espaço possível (o heap não é utilizado) precisamente graças à estrutura de links destinada a pessoas obcecadas com desempenho:
public unsafe ref struct UnmanagedArray<T> where T : unmanaged { private T* data; public UnmanagedArray(int length) { data = // get memory from some pool } public ref T this[int index] { get { return ref data[index]; } } public void Dispose() { // return memory to the pool } }
Como o wrapper contém um recurso não gerenciado, usamos o método Dispose para limpá-lo após o uso. Portanto, o exemplo se parece com isso:
static void Main(string[] args) { var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); array.Dispose(); }
Isso é inconveniente porque você precisa se lembrar de chamar Dispose. Além disso, é uma decisão dolorosa, pois o tratamento adequado de exceções não é aplicável aqui. Portanto, para que Dispose seja chamado de dentro, a instrução using foi introduzida. No entanto, anteriormente, como já mencionado, era impossível aplicá-lo nessa situação.
Mas no C # 8.0, você pode tirar o máximo proveito da instrução using:
static void Main(string[] args) { using (var array = new UnmanagedArray<int>(10)) { Console.WriteLine(array[0]); } }
Ao mesmo tempo, o código se tornou mais conciso, graças às declarações:
static void Main(string[] args) { using var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); }
Os outros dois exemplos abaixo (grande parte do código omitido por questões de brevidade) são retirados do repositório CoreFX.
O primeiro exemplo é a estrutura de referência ValueUtf8Converter, que agrupa uma matriz de bytes [] de um conjunto de matrizes:
internal ref struct ValueUtf8Converter { private byte[] _arrayToReturnToPool; ... public ValueUtf8Converter(Span<byte> initialBuffer) { _arrayToReturnToPool = null; } public Span<byte> ConvertAndTerminateString(ReadOnlySpan<char> value) { ... } public void Dispose() { byte[] toReturn = _arrayToReturnToPool; if (toReturn != null) { _arrayToReturnToPool = null; ArrayPool<byte>.Shared.Return(toReturn); } } }
O segundo exemplo é o RegexWriter, que envolve duas estruturas de referência ValueListBuilder que precisam ser limpas explicitamente (já que também gerenciam matrizes do pool de matrizes):
internal ref struct RegexWriter { ... private ValueListBuilder<int> _emitted; private ValueListBuilder<int> _intStack; ... public void Dispose() { _emitted.Dispose(); _intStack.Dispose(); } }
Conclusão
Estruturas removíveis referenciadas podem ser consideradas tipos de pouco espaço que possuem um destruidor REAL, como no C ++. Ele será chamado assim que a instância correspondente for além do escopo da instrução using (ou escopo no caso de uma declaração using).
Obviamente, eles não se tornarão populares de repente ao escrever programas regulares, orientados para o comércio, mas se você estiver criando código de alto desempenho e baixo nível, deve saber sobre eles.
E também temos um artigo sobre nossa conferência:
