Esse é o 21º post da série sobre C# 7, e o sétimo sobre C# 7.2. Pra acompanhar a série você pode seguir a tag C#7 no blog ou voltar no post que agrega a série.

Lembrando que para utilizar as versões minor do C# (como a 7.1, ou 7.2) você precisa habilitá-la nos atributos do projeto. Veja neste post como fazê-lo e também como habilitar na solution inteira pra não ter que ficar configurando cada projeto individualmente.

Métodos de extensão com in

A partir da versão 7.2 do C# é possível quem um método de extensão receba por referência o primeiro parâmetro (o parâmetro com this), desde que este seja uma struct (não pode ser uma class).

A passagem pode ser com ref, onde a referência é mutável, ou com in, onde a referência não é mutável.

Isso é especialmente importante para evitar cópias de valores. Caso seja utilizado in é preciso utilizar uma readonly struct para evitar a cópia, exatamente como em um método que não é de extensão.

No exemplo a seguir você vê um método de extensão que soma dois pontos utilizando o modificador in, e sua subsequente utilização no método In:

public static Ponto Soma(in this Ponto p1, in Ponto p2) =>
    new Ponto(p1.X + p2.X, p1.Y + p2.Y);

void In()
{
    Ponto p1 = new Ponto(1, 2), p2 = new Ponto(3, 4);
    var pResultado = p1.Soma(p2);
    var pResultado2 = p1.Soma(new Ponto(3, 4));
    var pResultado3 = new Ponto().Soma(new Ponto(3, 4));
}

Essa operação não gera cópia de dados. Note também que podemos utilizar expressões com argumentos. Tanto para o tipo que será extendido (para o parâmetro com this), quanto para os outros parâmetros. As duas últimas somas no exemplo acima mostram esses casos.

Métodos de extensão com ref

No exemplo abaixo temos um exemplo um pouco diferente. Neste caso, estamos usando um PontoMutavel, que é uma struct, mas não uma readonly struct, ou seja, ela não é mutável. Esse PontoMutavel permite que alteremos os valores de X e Y.

Estamos fazendo uma subtração, só que, neste caso, in place, ou seja, em vez de retornar um novo PontoMutavel, estamos alterando o recebedor diretamente, alterando seus valores X e Y, veja como fica:

public static void Subtrai(ref this PontoMutavel p1, ref PontoMutavel p2)
{
    p1.X -= p2.X;
    p1.Y -= p2.Y;
}

void Ref()
{
    PontoMutavel p1 = new PontoMutavel(1, 2), p2 = new PontoMutavel(3, 4);
    p1.Subtrai(ref p2);
    p1.Subtrai(ref new Ponto()); // não compila
    new Ponto().Subtrai(ref p1); // não compila
}

Este exemplo também não gera cópia de dados. O ref nos parâmetros indica que o PontoMutavel pode ser alterado, então, independentemente se o PontoMutavel fosse mutável ou não, não há necessidade de cópia. Fiz ele mutável para que ficasse claro que é possível alterá-lo.

Métodos de extensão genéricos com ref

É possível também utilizar ref com parâmetros genéricos (mas não in). Lembrando que o objetivo é diminuir as cópias, nada impede o uso de ref, que traz um objeto que poderemos mudar. A única observação é que acrescentemos uma restrição de tipos, demandando que o tipo genérico seja uma struct. Por exemplo:

public static void M(ref this T p1) where T : struct { }

Você consegue ler um pouco mais sobre este assunto somente no Github. Não encontrei docs sobre isso no Microsoft Docs, então, só lá mesmo.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.