Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O SDK do .NET torna possível reduzir o tamanho de aplicativos autônomos cortando. O corte remove o código não utilizado do aplicativo e suas dependências. Nem todo o código é compatível com o corte. O .NET fornece avisos de análise de corte para detetar padrões que podem quebrar aplicativos cortados. Este artigo:
- Descreve como preparar bibliotecas para corte.
- Fornece recomendações para resolver avisos de corte comuns.
Pré-requisitos
SDK do .NET 8 ou posterior.
Ativar avisos de corte de biblioteca
Os avisos de corte em uma biblioteca podem ser encontrados com um dos seguintes métodos:
- Habilitando o corte específico do projeto usando a
IsTrimmablepropriedade. - Criar um aplicativo de teste de corte que usa a biblioteca e habilitar o corte para o aplicativo de teste. Não é necessário fazer referência a todas as APIs na biblioteca.
Recomendamos o uso de ambas as abordagens. O corte específico do projeto é conveniente e mostra avisos de corte para um projeto, mas depende das referências marcadas como compatíveis com o corte para ver todos os avisos. Cortar um aplicativo de teste é mais trabalhoso, mas mostra todos os avisos.
Habilitar corte específico do projeto
Definido <IsTrimmable>true</IsTrimmable> no arquivo de projeto.
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
Definir a propriedade IsTrimmable MSBuild para true marcar o assembly como "trimmable" e habilita avisos de corte. "Trimmable", o projeto:
- É considerado compatível com o corte.
- Não deve gerar avisos relacionados ao acabamento durante a construção. Quando usado em um aplicativo cortado, o assembly tem seus membros não utilizados cortados na saída final.
O IsTrimmable padrão da propriedade é ao true configurar um projeto como compatível com AOT com <IsAotCompatible>true</IsAotCompatible>. Para obter mais informações, consulte Analisadores de compatibilidade AOT.
Para gerar avisos de corte sem marcar o projeto como compatível com corte, use <EnableTrimAnalyzer>true</EnableTrimAnalyzer> em vez de <IsTrimmable>true</IsTrimmable>.
Verifique se os conjuntos referenciados são compatíveis com trim
Quando ativa a análise de trim para uma biblioteca, pode opcionalmente ativar a verificação de que todos os assemblies referenciados também estão anotados para serem compatíveis com o trim, ao definir a propriedade VerifyReferenceTrimCompatibility para true:
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<VerifyReferenceTrimCompatibility>true</VerifyReferenceTrimCompatibility>
</PropertyGroup>
Quando esta propriedade está ativada, o analisador emite um aviso sobre qualquer assembly referenciado que não possua o IsTrimmable metadado. Isto ajuda a garantir que todas as dependências do seu projeto são anotadas para compatibilidade com trims. O aviso emitido é IL2125.
Esta verificação é opcional porque:
- Nem todas as bibliotecas compatíveis com trim foram atualizadas para incluir os
IsTrimmablemetadados. - O aviso pode ser ruidoso se tiveres muitas dependências que funcionam corretamente com o trimming mas não estão explicitamente marcadas como tal.
Considere ativar esta verificação quando quiser garantir que todas as suas dependências são explicitamente marcadas como compatíveis com trim pelos seus autores.
Mostrar todos os avisos com o aplicativo de teste
Para mostrar todos os avisos de análise para uma biblioteca, o trimmer deve analisar a implementação da biblioteca e de todas as dependências que a biblioteca usa.
Ao criar e publicar uma biblioteca:
- As implementações das dependências não estão disponíveis.
- Os assemblies de referência disponíveis não têm informações suficientes para o aparador determinar se são compatíveis com o corte.
Devido às limitações de dependência, um aplicativo de teste autônomo que usa a biblioteca e suas dependências deve ser criado. O aplicativo de teste inclui todas as informações que o aparador requer para emitir um aviso sobre incompatibilidades de corte em:
- O código da biblioteca.
- O código ao qual a biblioteca faz referência a partir de suas dependências.
Nota
Se a biblioteca tiver um comportamento diferente dependendo da estrutura de destino, crie um aplicativo de teste de corte para cada uma das estruturas de destino que oferecem suporte a corte. Por exemplo, se a biblioteca usa compilação condicional , como #if NET7_0 para alterar o comportamento.
Para criar o aplicativo de teste de corte:
- Crie um projeto de aplicativo de console separado.
- Adicione uma referência à biblioteca.
- Modifique o projeto semelhante ao projeto mostrado abaixo usando a seguinte lista:
Se a biblioteca tiver como alvo um TFM que não é trimmable, por exemplo net472 ou netstandard2.0, não há nenhum benefício em criar um aplicativo de teste de corte. O corte só é suportado para .NET 6 e posterior.
- Adicionar
<PublishTrimmed>true</PublishTrimmed>. - Adicione uma referência ao projeto de biblioteca com
<ProjectReference Include="/Path/To/YourLibrary.csproj" />. - Especifique a biblioteca como um assembly raiz de aparador com
<TrimmerRootAssembly Include="YourLibraryName" />.-
TrimmerRootAssemblygarante que cada parte da biblioteca seja analisada. Diz ao aparador que esta montagem é uma "raiz". Um assembly "raiz" significa que o trimmer analisa cada chamada na biblioteca e percorre todos os caminhos de código que se originam desse assembly.
-
Arquivo .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
<TrimmerRootAssembly Include="MyLibrary" />
</ItemGroup>
</Project>
Depois que o arquivo de projeto for atualizado, execute dotnet publish com o identificador de tempo de execução de destino (RID).
dotnet publish -c Release -r <RID>
Siga o padrão anterior para várias bibliotecas. Para ver avisos de análise de corte para mais de uma biblioteca de cada vez, adicione-os todos ao mesmo projeto como ProjectReference e TrimmerRootAssembly itens. Adicionar todas as bibliotecas ao mesmo projeto com ProjectReference e TrimmerRootAssembly itens avisa sobre dependências se qualquer uma das bibliotecas raiz usar uma API de corte hostil em uma dependência. Para ver avisos que têm a ver apenas com uma biblioteca específica, consulte apenas essa biblioteca.
Nota
Os resultados da análise dependem dos detalhes de implementação das dependências. A atualização para uma nova versão de uma dependência pode introduzir avisos de análise:
- Se a nova versão adicionou padrões de reflexão não compreendidos.
- Mesmo que não houvesse alterações na API.
- A introdução de avisos de análise de corte é uma alteração importante quando a biblioteca é usada com
PublishTrimmedo .
Resolver avisos de corte
As etapas anteriores produzem avisos sobre o código que pode causar problemas quando usado em um aplicativo cortado. Os exemplos a seguir mostram os avisos mais comuns com recomendações para corrigi-los.
CódigoRequerNãoReferenciado (if localization or explanation is required) Otherwise, maintain as: RequiresUnreferencedCode
Considere o código a seguir que usa [RequiresUnreferencedCode] para indicar que o método especificado requer acesso dinâmico ao código que não é referenciado estaticamente, por exemplo, através de System.Reflection.
public class MyLibrary
{
public static void MyMethod()
{
// warning IL2026 :
// MyLibrary.MyMethod: Using 'MyLibrary.DynamicBehavior'
// which has [RequiresUnreferencedCode] can break functionality
// when trimming app code.
DynamicBehavior();
}
[RequiresUnreferencedCode(
"DynamicBehavior is incompatible with trimming.")]
static void DynamicBehavior()
{
}
}
O código realçado anterior indica que a biblioteca chama um método que foi explicitamente anotado como incompatível com o corte. Para se livrar do aviso, considere se MyMethod precisa ligar DynamicBehaviorpara . Em caso afirmativo, anote o chamador MyMethod com [RequiresUnreferencedCode] o qual propaga o aviso para que os chamadores recebam MyMethod um aviso:
public class MyLibrary
{
[RequiresUnreferencedCode("Calls DynamicBehavior.")]
public static void MyMethod()
{
DynamicBehavior();
}
[RequiresUnreferencedCode(
"DynamicBehavior is incompatible with trimming.")]
static void DynamicBehavior()
{
}
}
Depois de propagar o atributo até a API pública, os aplicativos chamam a biblioteca:
- Receba avisos apenas para métodos públicos que não são trimmable.
- Não receba avisos como
IL2104: Assembly 'MyLibrary' produced trim warnings.
Membros DynamicallyAccessed.
public class MyLibrary3
{
static void UseMethods(Type type)
{
// warning IL2070: MyLibrary.UseMethods(Type): 'this' argument does not satisfy
// 'DynamicallyAccessedMemberTypes.PublicMethods' in call to
// 'System.Type.GetMethods()'.
// The parameter 't' of method 'MyLibrary.UseMethods(Type)' doesn't have
// matching annotations.
foreach (var method in type.GetMethods())
{
// ...
}
}
}
No código anterior, UseMethods está chamando um método de reflexão que tem um [DynamicallyAccessedMembers] requisito. O requisito estabelece que os métodos públicos do tipo estão disponíveis. Satisfazer o requisito adicionando o mesmo requisito ao parâmetro de UseMethods.
static void UseMethods(
// State the requirement in the UseMethods parameter.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
// ...
}
Agora, todas as chamadas para UseMethods produzir avisos se passarem em valores que não satisfaçam o PublicMethods requisito. Semelhante ao [RequiresUnreferencedCode], depois de propagar esses avisos para APIs públicas, você está pronto.
No exemplo a seguir, um Type desconhecido flui para o parâmetro de método anotado. O desconhecido Type é de um campo:
static Type type;
static void UseMethodsHelper()
{
// warning IL2077: MyLibrary.UseMethodsHelper(Type): 'type' argument does not satisfy
// 'DynamicallyAccessedMemberTypes.PublicMethods' in call to
// 'MyLibrary.UseMethods(Type)'.
// The field 'System.Type MyLibrary::type' does not have matching annotations.
UseMethods(type);
}
Da mesma forma, aqui o problema é que o campo type é passado para um parâmetro com esses requisitos. É corrigido adicionando [DynamicallyAccessedMembers] ao campo.
[DynamicallyAccessedMembers] avisa sobre o código que atribui valores incompatíveis ao campo. Às vezes, esse processo continua até que uma API pública seja anotada, e outras vezes termina quando um tipo concreto flui para um local com esses requisitos. Por exemplo:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
static Type type;
static void UseMethodsHelper()
{
MyLibrary.type = typeof(System.Tuple);
}
Neste caso, a análise de corte mantém métodos públicos de Tuple, e produz avisos adicionais.
Recomendações
- Evite a reflexão sempre que possível. Ao usar a reflexão, minimize o escopo da reflexão para que ela seja acessível apenas a partir de uma pequena parte da biblioteca.
- Anote o código com
DynamicallyAccessedMemberspara expressar estaticamente os requisitos de corte quando possível. - Considere reorganizar o código para fazê-lo seguir um padrão analisável que possa ser anotado com
DynamicallyAccessedMembers - Quando o código for incompatível com o corte, anote-o e
RequiresUnreferencedCodepropague essa anotação para chamadores até que as APIs públicas relevantes sejam anotadas. - Evite usar código que usa reflexão de uma forma não compreendida pela análise estática. Por exemplo, a reflexão em construtores estáticos deve ser evitada. O uso de reflexão estaticamente não analisável em construtores estáticos resulta na propagação do aviso para todos os membros da classe.
- Evite anotar métodos virtuais ou métodos de interface. A anotação de métodos virtuais ou de interface requer que todas as substituições tenham anotações correspondentes.
- Se uma API for praticamente incompatível, talvez seja necessário considerar abordagens de codificação alternativas à API. Um exemplo comum são serializadores baseados em reflexão. Nesses casos, considere adotar outras tecnologias, como geradores de código-fonte, para produzir código que seja mais facilmente analisado estaticamente. Por exemplo, consulte Como usar a geração de código-fonte em System.Text.Json.
Resolver avisos para padrões não analisáveis
É melhor resolver avisos expressando a intenção do seu código usando [RequiresUnreferencedCode] e DynamicallyAccessedMembers quando possível. No entanto, em alguns casos, você pode estar interessado em habilitar o corte de uma biblioteca que usa padrões que não podem ser expressos com esses atributos, ou sem refatoração de código existente. Esta seção descreve algumas maneiras avançadas de resolver avisos de análise de corte.
Aviso
Estas técnicas podem alterar o comportamento do seu código ou resultar em exceções em tempo de execução se forem usadas incorretamente.
SupressãoIncondicionalDeMensagem
Considere o código que:
- A intenção não pode ser expressa com as anotações.
- Gera um aviso, mas não representa um problema real em tempo de execução.
Os avisos podem ser suprimidos por UnconditionalSuppressMessageAttribute. Isso é semelhante ao , mas persiste na IL e é respeitado durante a SuppressMessageAttributeanálise de corte.
Aviso
Ao suprimir avisos, você é responsável por garantir a compatibilidade de corte do código com base em invariantes que você sabe serem verdadeiras por inspeção e testes. Tenha cuidado com essas anotações, porque se elas estiverem incorretas, ou se invariantes do seu código forem alteradas, elas podem acabar escondendo o código incorreto.
Por exemplo:
class TypeCollection
{
Type[] types;
// Ensure that only types with preserved constructors are stored in the array
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type this[int i]
{
// warning IL2063: TypeCollection.Item.get: Value returned from method
// 'TypeCollection.Item.get' can't be statically determined and may not meet
// 'DynamicallyAccessedMembersAttribute' requirements.
get => types[i];
set => types[i] = value;
}
}
class TypeCreator
{
TypeCollection types;
public void CreateType(int i)
{
types[i] = typeof(TypeWithConstructor);
Activator.CreateInstance(types[i]); // No warning!
}
}
class TypeWithConstructor
{
}
No código anterior, a propriedade indexer foi anotada para que o retornado Type atenda aos requisitos de CreateInstance. Isso garante que o TypeWithConstructor construtor seja mantido e que a chamada para CreateInstance não avise. A anotação setter do Type[] indexador garante que todos os tipos armazenados no tenham um construtor. No entanto, a análise não é capaz de ver isso e produz um aviso para o getter, porque ele não sabe que o tipo retornado tem seu construtor preservado.
Se tiver certeza de que os requisitos foram atendidos, você pode silenciar este aviso adicionando [UnconditionalSuppressMessage] ao getter:
class TypeCollection
{
Type[] types;
// Ensure that only types with preserved constructors are stored in the array
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type this[int i]
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2063",
Justification = "The list only contains types stored through the annotated setter.")]
get => types[i];
set => types[i] = value;
}
}
class TypeCreator
{
TypeCollection types;
public void CreateType(int i)
{
types[i] = typeof(TypeWithConstructor);
Activator.CreateInstance(types[i]); // No warning!
}
}
class TypeWithConstructor
{
}
É importante sublinhar que só é válido suprimir um aviso se houver anotações ou código que garantam que os membros refletidos sejam alvos visíveis de reflexão. Não é suficiente que o membro tenha sido alvo de uma chamada, campo ou acesso à propriedade. Pode parecer ser o caso às vezes, mas esse código está fadado a quebrar eventualmente à medida que mais otimizações de corte são adicionadas. Propriedades, campos e métodos que não são alvos visíveis de reflexão podem ser embutidos, ter seus nomes removidos, ser movidos para diferentes tipos ou de outra forma serem otimizados de maneiras que interrompam a reflexão sobre eles. Ao suprimir um aviso, só é permitido refletir sobre alvos que eram alvos visíveis de reflexão para o analisador de corte em outro lugar.
// Invalid justification and suppression: property being non-reflectively
// used by the app doesn't guarantee that the property will be available
// for reflection. Properties that are not visible targets of reflection
// are already optimized away with Native AOT trimming and may be
// optimized away for non-native deployment in the future as well.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2063",
Justification = "*INVALID* Only need to serialize properties that are used by"
+ "the app. *INVALID*")]
public string Serialize(object o)
{
StringBuilder sb = new StringBuilder();
foreach (var property in o.GetType().GetProperties())
{
AppendProperty(sb, property, o);
}
return sb.ToString();
}
Dependência Dinâmica
O [DynamicDependency] atributo pode ser usado para indicar que um membro tem uma dependência dinâmica de outros membros. Isso faz com que os membros referenciados sejam mantidos sempre que o membro com o atributo é mantido, mas não silencia os avisos por conta própria. Ao contrário dos outros atributos, que informam a análise de corte sobre o comportamento de reflexão do código, [DynamicDependency] mantém apenas outros membros. Isso pode ser usado em conjunto com [UnconditionalSuppressMessage] para corrigir alguns avisos de análise.
Aviso
Use [DynamicDependency] o atributo apenas como último recurso quando as outras abordagens não forem viáveis. É preferível expressar o comportamento de reflexão usando [RequiresUnreferencedCode] ou [DynamicallyAccessedMembers].
[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
helper.Invoke(null, null);
}
Sem DynamicDependency, o corte pode remover Helper de MyAssembly ou remover MyAssembly completamente, se não estiver referido noutro local, produzindo um aviso que indica uma possível falha durante a execução. O atributo garante que Helper seja mantido.
O atributo especifica os membros a serem mantidos via string a ou via DynamicallyAccessedMemberTypes. O tipo e o assembly estão implícitos no contexto do atributo ou explicitamente especificados no atributo (por Type, ou por strings para o tipo e o nome do assembly).
As cadeias de caracteres de tipo e membro usam uma variação do formato de cadeia de caracteres de ID de comentário da documentação do C#, sem o prefixo do membro. A cadeia de caracteres de membro não deve incluir o nome do tipo de declaração e pode omitir parâmetros para manter todos os membros do nome especificado. Alguns exemplos do formato são mostrados no código a seguir:
[DynamicDependency("MyMethod()")]
[DynamicDependency("MyMethod(System,Boolean,System.String)")]
[DynamicDependency("MethodOnDifferentType()", typeof(ContainingType))]
[DynamicDependency("MemberName")]
[DynamicDependency("MemberOnUnreferencedAssembly", "ContainingType"
, "UnreferencedAssembly")]
[DynamicDependency("MemberName", "Namespace.ContainingType.NestedType", "Assembly")]
// generics
[DynamicDependency("GenericMethodName``1")]
[DynamicDependency("GenericMethod``2(``0,``1)")]
[DynamicDependency(
"MethodWithGenericParameterTypes(System.Collections.Generic.List{System.String})")]
[DynamicDependency("MethodOnGenericType(`0)", "GenericType`1", "UnreferencedAssembly")]
[DynamicDependency("MethodOnGenericType(`0)", typeof(GenericType<>))]
O [DynamicDependency] atributo é projetado para ser usado em casos em que um método contém padrões de reflexão que não podem ser analisados mesmo com a ajuda de DynamicallyAccessedMembersAttribute.