.NET: Brincando com ref structs
Tags: csharp, struct, performance
Published: 2024-04-03 20:49:26


Dias atrás, resolvi brincar com [ref structs](https://learn.microsoft.com/pt-br/dotnet/csharp/language-reference/builtin-types/ref-struct), fazendo alguns experimentos e pensando em como reduzir o [_boilerplate_](https://www.freecodecamp.org/portuguese/news/o-que-e-um-boilerplate-e-por-que-o-usamos-a-necessidade-de-um-guia-de-estilo-de-codigo/) de usar _try/finally_ para trocar o valor de uma variável Booleana. Foi um processo divertido. Como vejo pouco conteúdo sobre `ref structs`, em pt-BR, resolvi compartilhá-lo aqui. Acredito que vai ficar um pouco grande, pois vou falar sobre vários aspectos que fiz e alguns problemas e soluções. Irei separar em subtítulos para facilitar a leitura.

> O código apresentado aqui é bem simples. Caso queira usá-lo em produção, sugiro adicionar alguns testes unitários para garantir o perfeito funcionamento.
> E O MAIS IMPORTATE, conte-me como foi!

## 1. O Problema

Diversas vezes, me vi escrevendo códigos que precisam de um valor Booleano para saber se a lógica deve ser executada ou não. Usualmente, algo assim:

```c#
class ViewModel
{
	private bool flag;

	void SomeCode()
	{
		if (flag)
			return;
			
		flag = true;
		try
		{
			// Código omitido...
		}
		finally
		{
			flag = false;
		}
	}
}
```

Imagine que teria de utilizar essa `flag` em vários lugares, métodos dentro dessa classe ou até esse mesmo padrão em outros lugares... Não gostaria de ter que ficar reescrevendo essa estrutura do try/finally, pois ela é bem extensa. Quero uma solução que eliminará esse _boilerplate_ e garantirá que não impactará a performance da aplicação. Se você já está mais familiarizado com performance, sabe que o ideal é evitar que existam alocações na memória _heap_. E para garantir isso vou precisar de usar `ref struct` (não confundir com `struct`).

> Mesmo que `struct` seja um `ValueType`, isso não é garantia que ela estará na memória _stack_. Com isso, aquela afirmação de que "ValueType vive na stack e ReferenceType vive na heap" é um equivoco enorme! 
> Existem muito mais nuances, [eis aqui uma discussão](https://github.com/dotnet/docs/issues/4561) do time que escreve a linguagem e o _runtime_ sobre esse tema, vale a pena a leitura.
> Apenas um exemplo: para provar meu ponto, uma `List<int>` terá seus valores, que são inteiros (`ValueType`s), alocados na memória _heap_.

## 2. Começando a solucionar o problema

Então, como dito anteriormente, quero garantir que a solução viva na memória _stack_, tendo o mínimo de impacto na performance da aplicação - tenhamos isso em mente. Antes de começar a implementar uma API, **EU** gosto de pensar em como gostaria de consumir esse código. Com isso definido, fica mais fácil trabalhar na implementação. Claro que pode haver mudanças durante o processo por conta de alguma limitação. Então, abaixo está o código que eu desejo escrever:

```c#
class ViewModel
{
	private bool flag;

	void SomeCode_Jeito1()
	{
		if (flag)
			return;
			
		using var _ = new MyRefStruct(ref flag, valueToSet: true);
		
		// Resto da lógica
	}
	
	void SomeCode_Jeito2()
	{
		if (flag)
			return;
			
		using(new MyRefStruct(ref flag, valueToSet: true))
		{
			// Resto da lógica
		}
		// Outra lógica
	}
}
```

1. Por padrão, todo `ValueType` é passado como cópia. Para garantir que a mudança de valor dentro de `MyRefStruct` seja propagada para o `flag` dentro da classe `ViewModel`, ele deve ser passado por referência.
2. Eu quero "setar" o valor da `flag` para `true` quando for começar a lógica, por isso adiciono o segundo parâmetro no construtor.
3. O `using` garantirá que tudo entre `using var _ ...` e o último `}` do método `SomeCode_Jeito1` ficará dentro de um `try/finally` e chamará o método `Dispose` dentro de `MyRefStruct`. Como você deve ter imaginado, dentro do `Dispose` mudaremos o valor de `flag` para `false`. No caso do método `SomeCode_Jeito2`, tudo que estiver entre as `{}` do `using` ficará dentro de um `try/finally`.

> Aproveitando, adivinhe onde a variável `flag` ficará [alocada](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmARgFgAoagNwEMoACMJgXibxgHcmBhABQBKANzVxVIgGYmpfkwDe1JiqYAHKAEsGGGE2AQIAGyYAzI/QDm7M/SMBnGGKqqmy1e5XT+wz4r+uDMwINqZ2js6urqQArBgAFpr2on4Avn4ZVOk0VEA=)? **Não** é na memória _stack_ 😉. 

Essa abordagem me deixa bem contente. Afinal, troquei todo aquele _boilerplate_ por apenas 1 linha. Com esses itens em mente, criaremos nossa `ref struct`.

## 3. Criando a ref struct

Você já deve saber que para usar o `using` eu preciso de implementar a interface [`IDisposable`](https://learn.microsoft.com/pt-br/dotnet/api/system.idisposable.dispose?view=net-8.0). É uma interface bem simples, que tem apenas um método na sua definição `void Dispose()`. Então, teríamos algo desse tipo:

```c#
ref struct HoldAndChangeValue : IDisposable {}
```

Mas, isso não compilará! Por quê?! Bom, quando uma `struct` implementa uma interface, ela pode ser alocada na memória _heap_, por meio do [_boxing_](https://learn.microsoft.com/pt-br/dotnet/csharp/programming-guide/types/boxing-and-unboxing). E como `ref struct`s **NÃO** podem ser alocadas na _heap_, de jeito nenhum, o compilador não permite que ela implemente interfaces.

> Sim, meu jovem _Padawan_, eu sabia dessa limitação, mas quis pregar uma peça em você. Consegui? Bom, se sim, agora, vou te ensinar algo bem bacana.

"Para nossa alegria", várias funcionalidades da linguagem C# são baseadas em [_duck type_](https://pt.wikipedia.org/wiki/Duck_typing). Logo, eu só preciso ter o método `public void Dispose(){}` definido no meu tipo e, _voilà_, podemos usá-lo. Para os outros tipos (`class`, `struct` etc), é necessário implementar a interface `IDisposable`.
Algumas funcionalidades do C# utilizam o _duck type_ , como: `foreach, async/await etc`. Inclusive, [eis aqui](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmABgFgAoUgRmqLIAIjaA6AJXwwEsBbGGwDCEPgAceAGxhQAyjIBuPMDADOAbgbNWAVk1UGtAJwAKAESAeDcAg+2YCU+w6YAiAQwwwAKv0EA5CAHc2AFkpSR5VGEg8ABN7BiMmFBJ9Vmc3T282P0CQyTCIqNiHAxpaJBYSFiQAUQRqAG9qJmamAHpWpgB5JgADIiMepjUxGRcoJhw+XqIkAEF/Fx53KEGYPAwAY4gmCKZt3jFtqIiAcxwYJjEoCAUZCammlvbpuYWlmVXpS4hoi4jxsz9FzAaRmNiPZpEADMLDKVXmi2WTAA4jAMAj3lATBgABbhJg8dZMBQuSTnWwQpiNKgtWksADsVTYThgkhcAE8TCSyTBbGxUei3ssTHEaS0AL7UcVAA=) um exemplo de como fazer com que isto: `await 42;`,  seja válido.

Baseado nas informações dos parágrafos anteriores e em como queremos usar em código, nossa `HoldAndChangeValue` ficará:

```c#
ref struct HoldAndChangeValue
{
    ref bool value;

    public HoldAndChangeValue(ref bool flag, bool valueToSet)
    {
        value = ref flag;
        value = valueToSet;
    }
    
    public void Dispose()
    {
        value = !value;
    }
}
```

E é isso! O código fica mais simples de escrever e o funcionamento é como esperado. Fique a vontade para verificar [aqui](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmABgFgAoUgRmqLIAIjakmXaA6AYQjwDOEADYwA3NUlUAbgEMoTaQFsmAXiZ4YAdyYA1AJbaAshAAmMYQAoAlBJlKuAZQhKYfczbvUA6lH0YYABl9TU8fPwDg0NspAHpYpkcYJghFWWFoJnMmADNhWQBzXMyMXBg0DQBj1Ll9JgAHPzwMeQ1ZU1kGiH0BNLrYDGg8eWp4pgyC1LxUyFcAc9TTVNdBpeplJxc3MxgwqgYSPUMtE3NhagBvagBIRv05AKZgCBFc/IKvKmuiAGYWFESW3cu2sNyuX2u+hyTEseUKoIh3wA7HYOGi0Tdrr5/EEQrsACQAIm4TAA8mkMgpsnCigBLpggJgXGkAX0JMQhjDSCgA+moNNomAAJESmACCeFMPAAFrI8AUYLp0jhdrBoTSKnJhCqACoQJJYJilFUc9Ho7GRPGWIkkLhkimZanvJj0xnM95sjnXa5jABKMAEgyynWEAGeCvowLIbizqLH9lQ1UxA7gwBhhaKJVLZfLFcqYJdqOik89XlqVZ90UW0b8M8JxZKZXKFUrtaqYNDS8I3oUKl2Kbr9TAMAj0eCzWjy8l1EmaaiJwPp4u9Qb5xx41WqOja0QAQARHr1CACEHVjjjhdT/kAQinlYXFtxoSJ5K1juSNJdDKZU5ZTDgTB7jAzRQIsqQAOQivWWZNrmrYquB7Jrkw8YskAA==).

> No link acima, tente implementar a estrutura do método `SomeCode_Jeito2`. Você perceberá que dentro das chaves o valor da `flag` será `True` e fora voltará para `False`.

> Acredito que vale a pena reforçar, que a `ref struct` possui diversas limitações de uso. Isso serve para garantir que ela não seja alocada na memória _heap_. Então, essa solução não funcionará em métodos que têm o modificador `async`, dentro de uma _lambda expression_ que capture essa `ref struct` em uma `closure`, dentre outros cenários.

## 4. Expandindo nossa ref struct

A solução proposta no capítulo anterior resolve boa parte dos cenários. Porém, não lida com propriedades. Para quem usa o padrão MVVM, por exemplo, seria muito interessante ter suporte a propriedades. Acredito que já tenha visto a estrutura do `IsBusy`. Caso não conheça, é semelhante ao exemplo que usei. Contudo, em vez de usar um `field` utiliza-se uma propriedade.
O maior problema é que não se pode passar propriedades como referência. Isso se deve ao fato de que propriedades, no C#, são um [_sugar syntax_](https://pt.wikipedia.org/wiki/A%C3%A7%C3%BAcar_sint%C3%A1tico) para métodos (_getter & setter_).
É possível ver isso [aqui](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBLANgHwAEAmARgFgAoQgZgAIS6BhOgbyqrq7oAcpsAbgEMMMOsAgRcdbAGcAQgFdZATwDcnbrXGTpASQXKVdTV3aVulugHMYGOgF4AfDMOqNFq11l3HLuUqqjnTCuIowHpYAvlRRQA=). Dentro do [IL (_Intermediated Language_)](https://en.wikipedia.org/wiki/Common_Intermediate_Language), você verá que não há um _valor_ para essa propriedade, como há para o _field_, mas métodos de _get & set_.

E qual a solução? Pelo que pesquisei e experimentei, isso só pode ser feito usando [`delegates`]([Delegados – Guia de Programação em C# | Microsoft Learn](https://learn.microsoft.com/pt-br/dotnet/csharp/programming-guide/delegates/)). Nesse caso, usei uma [`Action`](https://learn.microsoft.com/pt-br/dotnet/api/system.action-1?view=net-8.0). Só que `Action`, assim como `delegates`, são `ReferenceTypes`. Logo, são alocados por padrão, na memória _heap_... E agora?

> Inclusive, se você souber outra maneira de copiar o comportamento do `ref` com propriedades, conte-me, estou muito interessado!

Agora, é só adicionar uma `Action` como parâmetro no construtor da `ref struct` e criar uma variável local para isso e vida que segue... Mas, espere aí... Como colocarei algo que é alocado na _heap_ dentro de algo que não pode ser? Bom, se você conseguiu ler e entender a discussão que fiz referência no começo do artigo, sobre onde se alocam `ValueType & ReferenceType`, ficará mais claro.

> Digo "conseguiu ler e entender", acima, pois toda a discussão está em inglês e eu sei que nem todo mundo tem proficiência nessa linguagem, em um país como o nosso é completamente aceitável esse cenário. Particularmente, **EU** digo que é muito importante praticar o inglês se quiser se tornar um(a) profissional melhor. De todo modo, acredito que as ferramentas que traduzem sites estejam bem melhores atualmente. Usem-nas sempre que possível. Mas, logo abaixo, eu explico melhor o "Eureka" desse caso em particular.

Continuando... Sim, nossa `ref struct` **NÃO** viverá na memória _heap_, mas ela pode referenciar objetos que viverão. Isso porque o tempo de vida desses objetos serão maiores do que o tempo de vida de nossa `ref struct`. Quando passarmos a `Action` para nossa `ref struct`, o que passaremos, na verdade, é o endereço de memória dessa `Action`, o qual fica na memória _stack_. Logo, ele pode viver tranquilamente na nossa `ref struct`. Para uma visualização gráfica do que estou falando, acesse [aqui](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmARgFgAoaosgTgAoBKAbmtpIAYOqB6AFQACagDkIQyAFsYeDAEOoASwkBDMEqmqhMBEoDOGGFKFSAlxggATCPqEAHVVG1K8++zA0Q8ToQCIIYAArGEt9P2pYaVkbIWBlPDBfaR19fRg7c0sbO0dnISMoIRwtITwIAHOAY4krGEkIKQkkq1VqGSl4iTMhaABzVTwlAC9VWJ9TYwBn5VU0IQN9CQA3VSUC+tV4pSKAWQBVAEkAFQB5IW0ZORgAQmoBPl48GAB3IQA1JVfd6xgAGxYADoAEJ/GAsdg0KikD5fF4/Op/agAb2oQnRQiIAGY4hAIH8hAAxP6qPpCZF9UKsdIYVgAXzRGOAeIJADMSWSALxCVmqP7pSEYkRUIXYzEoISg8HMRno1EioUYohcISrIrAVQYTXabnPN4ACXxVgAgngrABhAAWg0p7z5OHB7NJ8wwuBg8xYQk5AD4iRyvUIbsTSWxeIqMYc3B4wBhAQBlLVgADWjA1Wq1oYV4dIAFY09rM7KhAyqCXqIZcDGhIa/iazVabTA7X8HUIQEJDgARAz2WxbMEoovM/Gq+0wQVKsgkBxQCD2OgkCfootimt1i3WvC2seplk8jnzYcE1YtmDHCBx0LzBcXGMqPAyrPkotCk+t7lOvpLxVv+rc3/npetIvhi9izvOU4BuoGD3t+JZCiuOJEBK3buLY0pFvK4bor+AY3L+35CmBc4LhCRYlnSQA===).

Com isso, veremos, primeiramente, como eu gostaria de consumir o código e, então, implementaremos a solução.

```c#
class ViewModel
{
    public bool IsBusy {get; set;} = false;
    public void Run()
    {
	    if (IsBusy)
		return;
	    
	    IsBusy = true;
		using var _ = new HoldAndChangeValueProperty(() => IsBusy = !IsBusy);
        
		// Código a ser executado   
    }
}
```

> Deixo para você remover a linha `IsBusy = true;` e a abstrair dentro da `ref struct`.

A implementação, nesse caso, será bem simples também. Basta apenas passar a `Action` para a `ref struct` e a executar dentro do método `Dispose`.

```c#
ref struct HoldAndChangeValueProperty
{
    Action propAction;
    
    public HoldAndChangeValue(Action action)
    {
        propAction = action;
    }
    
    public void Dispose()
    {
        propAction();
    }
}
```

Para ver o código em execução, basta acessar [aqui](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEUCuA7AHwAEAmARgFgAoIgBgAIiylGyA6AYQjwGcIAbGAG5q1JgE4AFAEoRVMSVqiqAegBU9agDkI9SAFsYeDAEOoAS10BDMOf1X6MBOZ4YY++voCXGCABMIHnoABysoB3NeYJhbbjD6ACIIYAArGF8eBOpYAyMA+mALPDB4g0ceHhgg718AoNDw+jcoehx7ejwIAHOAY90/GD0IfV0SvytqQ31C3S96aABzKzxzAC8rfLwHKYBniys0ehc+egA3K3MmwatC8xaAWQBVAEkAFQB5em2jNwBCajUKmU5xapw8AF4OjAAO70ABq5hh938MH4MjkYLYACV8OjqAB1CxuAAykRgeKohPMJLJFMxOLwFIU8MR0ORA341AA3tR6HzGABmAoQAT0Z48ABCOB4AE96FyFukhJUMEIAL68/mavlEIVEFD0BkybXyk38+hUml4ckAEgS4qlssOyzcQWAVgwHoO9DmIHlDulMrVCVkZq1VHN5vMADN6JIA7LpGHI4wAOxyFN85NiyWB+iQjC4YTZ7N0M7xd2ez35qGwgASAj8AEE8H4OAALZaKuFWfg4GAABSgEGiUAwMskMnzAD4c465ZDfgmZaGI5nLTBSdbJHbl4cBsEIC4Cl7PYdff7c7Lg6vzRq11mH5on7rGAahxBzsanzyn+aN1utr2leMrOsYVQnlW3oXlyy43hmmbZjGcbLkmf6ZkQ6YluhfLLjWhb9ghKbZiCkFejW1r1o2LZtp2eDdr2/YfqO46TtIM5znmi6oURkYAbSu4gfuMCHseAxkWePr0H6sEgfB2GZvQKgqPQHZWPYra6LoAAiLiHpUnjLDgvaGOBhwGbkxjxFUPAOPwkSdtmynzJ4Pj+LoPA7PQlRQCYjgIDEOCegEhwGMEgieqZbiHGQZwwKsbDZpWXpsLpPD6eSt7rkSm4CcB87CaJbqnlYqV6YEmXntJl7zvJT73ve2QwLGri4GAGD0A2/DNq2an0TAPZ9oOw4sTK3ImkwJAhCNk28RNQpdT1tFdgNjHDSOMBjhOk1fO1lh4Gh5q/opwQzWQU2QjYGD7bx97mvNb70GlGXfkd2anSOk0Une1BqkAA). 

Para dar suporte ao uso de propriedades, a solução, como um todo, tocará na memória _heap_, pois precisaremos de uma `Action` para fazê-lo funcionar corretamente. Alocando uma única `Action` é algo tranquilo, pois o tamanho será de apenas alguns `bytes`. O problema é se isso for um trecho de código muito executado, daí pode ser necessário verificar o impacto de performance. E é exatamente isso que vou falar no próximo capítulo.

## 5. Alocações implícitas (_Implicit Allocations_)

Basicamente, alocações implícitas são quando algo vai ser armazenado na memória _heap_, mas sem que isso seja mostrado diretamente. Por exemplo, `var obj = new object();`, essa chamada está alocando um novo `objeto` na memória _heap_. Isso é **explicito**, pois estamos instanciando esse objeto com o uso da _keyword_ `new`.
Agora, `object i = 10;` é uma alocação **implícita**, por meio de _boxing_. Estou convertendo o inteiro 10 em um `ReferenceType`, que será armazenado na memória _heap_. Se quiser aprofundar no tema, recomendo [este artigo](https://devblogs.microsoft.com/pfxteam/know-thine-implicit-allocations/) do Stephen Toub.

No nosso caso, onde acontece essa alocação? Ela ocorre quando passamos a `Action` para a `ref struct`. Se observamos o que o compilador gera pelo [código](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY5Lk4B0AwhAHYDOEANjANyGEUCcACgCU3AryzEeBAPQAqUoQByEUpAC2MRgBcAh1ACWKgIZh9ao6RgJ9zLTDWk1AS60QAJhGakADkagX9Fm8YUyY/UgAiCGAAKxhXZgjCWHVND1JgA0YwcPVLZmYYL2dXDy9ff1I7KFIAV3NSRggAc4BjlTcYVQg1FRy3I0INNUyVJ1JoAHMjRn0ALyN0xgthgGeDIwAaUhtWUgA3I30qrqNM/RqAWQBVAEkAFQB5UhXNOwBCQllpKUOa/YcAF5GjAAO6kABq+jBl3cMDYwlEAJoACVaoxEYQAOoGOwAGUCMExBBx+nxhOJyLRGJEUmwkOhoNhnTYhAA3oRSFzyABmDIQdikW7MABCtWYAE9SGzJvFOIUtJwAL6c7mqrkYPkYFCkanCdXSg0KAjc03Gs3c/QAM1IAmFYslQiNFvIAHZRC7zS77eKpcCtFBalxnVyQ+QyH8MkYtNGLMDGGDSAAJdhuACCjDcdAAFjNZRCjGwgwAFKAQYJQLQSgTCUiAgB8QtFvrrpHePsdHtNBpVBF7yRgNtsgbAWmTqYzWdzjHzhZLZYrVfZBooWB8C9XXdDJu5mvHbHTmZzeZgBaLMFL5Zglerq5eo8MjCdO65HJfZu8G5wa+BJi0j63Uhe27d8921UgABEbG8TwiWfU0309T9y1XYlTV7JUgA==), você verá que o compilador reescreve boa parte do nosso código, mas a principal, nesse cenário, é esta linha:

```c#
// O que escrevemos
using var _ = new HoldAndChangeValueProperty(() => IsBusy = !IsBusy);
```

Será reescrita em:
```c#
// Como o compilador reescreve
HoldAndChangeValueProperty holdAndChangeValueProperty = new HoldAndChangeValueProperty(new Action(<Run>b__4_0));
```

Onde o `<Run>b__4_0` é um método gerado pelo compilador. Olhando essa linha, é possível perceber que toda vez que esse método é executado, será alocada uma nova `Action`, que executará o mesmo método... Isso é bem ineficiente, afinal se o método é o mesmo por que não fazer um _cache_?

> Você já deve ter ouvido/lido a seguinte frase "compiladores são burros". Se não ouviu, acredito que ouvirá/lerá em algum momento da jornada como pessoa desenvolvedora. Eu concordei com essa frase por muito tempo. Agora, eu penso de outra maneira. Eu diria que os compiladores são conservadores, eles preferem, por padrão, garantir o funcionamento correto em vez de otimizações.

Como, por padrão, compiladores preferem garantir estabilidade em vez de otimizações. Esse tipo de cenário é bem comum em vários pontos de qualquer linguagem.

### 5.1 Entendendo e solucionado o problema

Vamos criar um cenário em que esse trecho de código será executado 10 mil vezes. O que você acha que ocorrerá? Exato, serão alocadas 10 mil `Action`s para fazer exatamente a mesma coisa. Para comprovar, criei um código de teste. O método que será utilizado para analisar as alocações é este:

```c#
	public void Run()
	{
		for (var i = 0; i < 10_000; i++)
		{
			viewModel.Run();
			if (i is 5_000)
			{
				Console.ReadLine();
			}
		}
	}
```

Ao final da execução, eis os resultados:

![Imagem do resultado de alocações de System.Action na classe ViewModel.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obq3it11tdd0n8deftec.png)

Como podemos ver a classe `ViewModel` no método `Run`, alocou 10 mil `Actions`, como eu havia dito. Para mim, esse comportamento é inaceitável. Mas, ele é um problema que está fora do escopo da nossa `ref struct`, pois quem cria essas alocações é o compilador. Para resolver isso, devemos criar, manualmente, o _cache_ dessa `Action` e usá-la. _Refatorando_ o código da `ViewModel`, teremos:

```c#
class ViewModel
{
	public bool IsBusy { get; set; } = false;

	readonly Action updateIsBusy;

	public ViewModel()
	{
		updateIsBusy = () => IsBusy = !IsBusy;
	}

	public void Run()
	{
		using (new HoldAndChangeValue(updateIsBusy))
		{
			// Código a ser executado
		}
	}
}
```

Com essa mudança, o código gerado pelo compilador será:

```c#
HoldAndChangeValueProperty holdAndChangeValueProperty = new HoldAndChangeValueProperty(updateIsBusy);
```

Como podemos ver, não existe mais uma nova alocação toda vez que o método `Run` rodar. Para comprovar, utilizaremos o mesmo código de testes nessa nova versão da classe `ViewModel`. Os resultados podem ser vistos na imagem abaixo:

![Imagem do resultado de alocações de System.Action na classe ViewModel.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7axk32gaf0yilzd6n13.png)

Como pode ser observado, apenas uma `Action` foi instanciada e será utilizada em todas as chamadas do método `ViewModel.Run`. 

## 6. Conclusão

Concluímos nossa aventura pela `ref struct`. Como você deve ter notado, existe bastante coisa para se entender além da `ref struct` por si só. Espero que as referências, aqui citadas, possam te ajudar nessa jornada e agregar ainda mais conhecimento.

Não tenha medo de utilizar `ref struct`s. É um recurso novo na linguagem, bem nichado também, mas vale a pena se familiarizar com ele e outros recursos. Afinal, eles permitem que possamos escrever códigos performáticos sem precisar de usar `unsafe`.

Por favor, conte-me suas aventuras com `ref struct`. Estou muito interessado no que você criará!