From 660e3e41bf4d2a308a7528d39af87208a3648141 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 6 Apr 2021 01:53:18 -0700 Subject: [PATCH 01/10] First draft of blog post on fixing warnings --- docs/fixing-warnings.md | 186 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/fixing-warnings.md diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md new file mode 100644 index 000000000000..023d9848a66d --- /dev/null +++ b/docs/fixing-warnings.md @@ -0,0 +1,186 @@ + +# Trim warnings in .NET 6 + +In .NET Core 3.1 and 5.0 we introduced trimming as a new preview feature for self-contained .NET +core applications. Conceptually, the feature is very simple: when publishing the application, the +.NET SDK analyzes the entire application, including the .NET framework, and removes all unused +code. In the time trimming has been in preview, we've learned that trimming is very powerful -- +it can reduce application size by half, or more. However, we've also learned about the +difficulties in safely trimming applications. + +The most difficult question in trimming is what is unused, or more precisely, what is used. For +most standard C# code this is trivial -- the trimmer can easily walk method calls, field and +property references, etc, and determine what code is used. Unfortunately, some features, like +reflection, present a significant problem. Consider the following code: + +```C# +string s = Console.ReadLine(); +Type type = Type.GetType(s); +foreach (var m in type.GetMethods()) +{ + Console.WriteLine(m.Name); +} +``` + +In this example, `Type.GetType` dynamically requests a type with an unknown name, and then prints +the names of all of its members. Because there's no way to know at publish time what type name is +going to be used, there's no way for the trimmer to know which type to preserve in the output. +It's very likely that this code could have worked before trimming (as long as the input is +something known to exist in the target framework), but would probably produce a null reference +exception after trimming (due to `Type.GetType` returning null). + +This is a frustrating situation. Trimming often works just fine, but occasionally it can produce +breaking behavior, sometimes in rare code paths, and it can be very hard to trace down the cause. + +For .NET 6 we want to bring a new feature to trimming: trim warnings. Trim warnings happen +because the trimmer sees a call which may access other code in the app, but the trimmer can't +determine which code. This could mean that the trimmer would trim away code which is used at +runtime. + +## Reacting to trim warnings + +Trim warnings are meant to bring predictability to trimming. The problem with trimming is that some +code patterns can depend on code in a way that isn't understandable by the trimmer. Whenever the +trimmer encounters code like that, that's when you should expect a trim warning. + +There are two big categories of warnings which you will likely see: + + 1. `RequiresUnreferencedCode` + 2. `DynamicallyAccessedMembers` + +### RequiresUnreferencedCode + +`RequiresUnreferencedCode` is simple: it's an attribute that can be placed on methods to indicate +that the method is not trim-safe, meaning that it might use reflection or some other mechanism +to access code that may be trimmed away. This attribute is used when it's not possible for the +trimmer to understand what's necessary, and a blanket warning is needed. This would often +be true for methods which use `dynamic`, RefEmit, or other runtime code generation technologies. +An example would be: + +```C# +[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")] +void MethodWithRefEmit() { ... } + +void TestMethod() +{ + // IL2026: Using method 'MethodWithUnreferencedCodeUsage' which has 'RequiresUnreferencedCodeAttribute' + // can break functionality when trimming application code. Use 'MethodFriendlyToTrimming' instead. + MethodWithRefEmit(); +} +``` + +There aren't many workarounds for `RequiresUnreferencedCode`. The best way is to avoid calling +the method at all when trimming and use something else which is trim-safe. If you're writing a +library and it's not in your control whether or not to call the method and you just want to +communicate to *your* caller, you can also add `RequiresUnreferencedCode` to your own method. +This silences all linker warnings in *your* code, but will produce a warning whenever someone +calls your method. + +If you can somehow determine that the call is safe, and all the code that's needed won't be +trimmed away, you can also suppress the warning using +[UnconditionalSuppressMessageAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.unconditionalsuppressmessageattribute?view=net-5.0). +For example: + +```C# +[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")] +void MethodWithRefEmit() { ... } + +[UnconditionalSuppressMessage("RefEmitTrimming", "IL2026:RequiresUnreferencedCode", + Justification = "I've verified the RefEmit code only adds integers, so it's safe")] +void TestMethod() +{ + MethodWithRefEmit(); // Warning suppressed +} +``` + +`UnconditionalSuppressMessage` is preserved into IL, so the trimmer can see the suppression even +after build and publish. Be very careful when suppressing trim warnings: it's possible that the +call may be trim-safe now, but as you change your code that may change and you may forget to +review all the suppressions. + +### DynamicallyAccessedMembers + +`DynamicallyAccessedMembers` is usually about reflection. Unlike `RequiresUnreferencedCode`, +reflection can sometimes be understood by the trimmer, as long as it's annotated correctly. +Let's take another look at the original example: + +```C# +string s = Console.ReadLine(); +Type type = Type.GetType(s); +foreach (var m in type.GetMethods()) +{ + Console.WriteLine(m.Name); +} +``` + +In the example above, the real problem is `Console.ReadLine()`. Because *any* type could +be read, the trimmer has no way to know if you need members on `System.Tuple` or `System.Guid` +or any other type. On the other hand, if your code looked like, + +```C# +Type type = typeof(System.Tuple); +foreach (var m in type.GetMethods()) +{ + Console.WriteLine(m.Name); +} +``` + +This would be fine. Here the trimmer can see the exact type being referenced: `System.Tuple`. Now +it can use flow analysis to determine that it needs to keep all public methods. So where does +`DynamicallyAccessMembers` come in? What happens if the reflection is split across methods? + +```C# +void M1() +{ + M2(typeof(System.Tuple)); +} +void M2(Type type) +{ + // Trim analysis warning IL2070: net6.Program.M2(Type): 'this' argument does not satisfy + // 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. The + // parameter 'type' of method 'net6.Program.M2(Type)' does not have matching annotations. The + // source value must declare at least the same requirements as those declared on the target + // location it is assigned to. + var members = type.GetMethods(); + ... +} +``` + +Now you see a warning. For performance and stability flow analysis isn't performed between +methods, so annotation is needed to pass information upward, from the reflection call +(`GetMethods`) to the source of the `Type` (`typeof`). In the above example, the trimmer warning +is stating that `GetMethods` requires the `PublicMethods` annotation on types, but the `type` +variable doesn't have the same requirement. In other words, we need to pass the requirements from +`GetMethods` up to the caller i.e., + +```C# +void M1() +{ + M2(typeof(System.Tuple)); +} +void M2( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) +{ + var members = type.GetMethods(); + ... +} +``` + +Now the warning disappears, because the trimmer knows exactly which members to preserve, and on +which type to preserve them on. In general, this is the best way to deal with +`DynamicallyAccessedMembers` warnings: add annotations so the trimmer knows what to preserve. + +As with `RequiresUnreferencedCode`, adding `RequiresUnreferencedCode` or +`UnconditionalSuppressMessage` attributes also works, but none of these options make the code +safe to trim, while adding `DynamicallyAccessedMembers` does. + +## Conclusion + +This description should cover the most common situations you end up in while trimming your +application. Over time we'll continue to improve the diagnostic experience and provide more streamlined +ways to make your code trim-safe. + +As we continue developing trimming we hope to see more code that's fully annotated, so users can +trim with confidence. Because trimming involves the whole application, trimming is as much a +feature of the ecosystem as it is of the product and we're depending on all developers to help +improve the ecosystem. From 49424385c4c7254881420743a561e6bfebc9f514 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Apr 2021 01:29:56 -0700 Subject: [PATCH 02/10] Update docs/fixing-warnings.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Strehovský --- docs/fixing-warnings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 023d9848a66d..36e1bdb02620 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -54,7 +54,7 @@ There are two big categories of warnings which you will likely see: that the method is not trim-safe, meaning that it might use reflection or some other mechanism to access code that may be trimmed away. This attribute is used when it's not possible for the trimmer to understand what's necessary, and a blanket warning is needed. This would often -be true for methods which use `dynamic`, RefEmit, or other runtime code generation technologies. +be true for methods which use the C# `dynamic` keyword, `Assembly.LoadFrom`, or other runtime code generation technologies. An example would be: ```C# From c98be03cc5b000c42e638084fa9de43d1a10c0e0 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Apr 2021 01:30:05 -0700 Subject: [PATCH 03/10] Update docs/fixing-warnings.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michal Strehovský --- docs/fixing-warnings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 36e1bdb02620..1b3d8226c6a7 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -73,7 +73,7 @@ There aren't many workarounds for `RequiresUnreferencedCode`. The best way is to the method at all when trimming and use something else which is trim-safe. If you're writing a library and it's not in your control whether or not to call the method and you just want to communicate to *your* caller, you can also add `RequiresUnreferencedCode` to your own method. -This silences all linker warnings in *your* code, but will produce a warning whenever someone +This silences all trimming warnings in *your* code, but will produce a warning whenever someone calls your method. If you can somehow determine that the call is safe, and all the code that's needed won't be From d08a430efaf79ec31849d7e42e188deb4e83854c Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Apr 2021 01:32:18 -0700 Subject: [PATCH 04/10] Update fixing-warnings.md --- docs/fixing-warnings.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 1b3d8226c6a7..955b02d1d857 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -59,13 +59,13 @@ An example would be: ```C# [RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")] -void MethodWithRefEmit() { ... } +void MethodWithAssemblyLoad() { ... } void TestMethod() { // IL2026: Using method 'MethodWithUnreferencedCodeUsage' which has 'RequiresUnreferencedCodeAttribute' // can break functionality when trimming application code. Use 'MethodFriendlyToTrimming' instead. - MethodWithRefEmit(); + MethodWithAssemblyLoad(); } ``` @@ -83,13 +83,13 @@ For example: ```C# [RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")] -void MethodWithRefEmit() { ... } +void MethodWithAssemblyLoad() { ... } -[UnconditionalSuppressMessage("RefEmitTrimming", "IL2026:RequiresUnreferencedCode", - Justification = "I've verified the RefEmit code only adds integers, so it's safe")] +[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", + Justification = "I've verified the loaded assembly only adds integers, so it's safe")] void TestMethod() { - MethodWithRefEmit(); // Warning suppressed + MethodWithAssemblyLoad(); // Warning suppressed } ``` From 1acd4dff6e7038d72ae4e41ca6b4e762edf53928 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Apr 2021 10:27:40 -0700 Subject: [PATCH 05/10] PR comments --- docs/fixing-warnings.md | 51 ++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 955b02d1d857..98b37cca73d8 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -23,7 +23,7 @@ foreach (var m in type.GetMethods()) ``` In this example, `Type.GetType` dynamically requests a type with an unknown name, and then prints -the names of all of its members. Because there's no way to know at publish time what type name is +the names of all of its methods. Because there's no way to know at publish time what type name is going to be used, there's no way for the trimmer to know which type to preserve in the output. It's very likely that this code could have worked before trimming (as long as the input is something known to exist in the target framework), but would probably produce a null reference @@ -51,10 +51,11 @@ There are two big categories of warnings which you will likely see: ### RequiresUnreferencedCode `RequiresUnreferencedCode` is simple: it's an attribute that can be placed on methods to indicate -that the method is not trim-safe, meaning that it might use reflection or some other mechanism -to access code that may be trimmed away. This attribute is used when it's not possible for the -trimmer to understand what's necessary, and a blanket warning is needed. This would often -be true for methods which use the C# `dynamic` keyword, `Assembly.LoadFrom`, or other runtime code generation technologies. +that the method is not trim-compatible, meaning that it might use reflection or some other +mechanism to access code that may be trimmed away. This attribute is used when it's not possible +for the trimmer to understand what's necessary, and a blanket warning is needed. This would often +be true for methods which use the C# `dynamic` keyword, `Assembly.LoadFrom`, or other runtime +code generation technologies. An example would be: ```C# @@ -63,18 +64,18 @@ void MethodWithAssemblyLoad() { ... } void TestMethod() { - // IL2026: Using method 'MethodWithUnreferencedCodeUsage' which has 'RequiresUnreferencedCodeAttribute' + // IL2026: Using method 'MethodWithAssemblyLoad' which has 'RequiresUnreferencedCodeAttribute' // can break functionality when trimming application code. Use 'MethodFriendlyToTrimming' instead. MethodWithAssemblyLoad(); } ``` There aren't many workarounds for `RequiresUnreferencedCode`. The best way is to avoid calling -the method at all when trimming and use something else which is trim-safe. If you're writing a -library and it's not in your control whether or not to call the method and you just want to -communicate to *your* caller, you can also add `RequiresUnreferencedCode` to your own method. -This silences all trimming warnings in *your* code, but will produce a warning whenever someone -calls your method. +the method at all when trimming and use something else which is trim-compatible. If you're +writing a library and it's not in your control whether or not to call the method and you just +want to communicate to *your* caller, you can also add `RequiresUnreferencedCode` to your own +method. This silences all trimming warnings in your code, but will produce a warning whenever +someone calls your method. If you can somehow determine that the call is safe, and all the code that's needed won't be trimmed away, you can also suppress the warning using @@ -86,22 +87,24 @@ For example: void MethodWithAssemblyLoad() { ... } [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode", - Justification = "I've verified the loaded assembly only adds integers, so it's safe")] + Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")] void TestMethod() { MethodWithAssemblyLoad(); // Warning suppressed } ``` -`UnconditionalSuppressMessage` is preserved into IL, so the trimmer can see the suppression even -after build and publish. Be very careful when suppressing trim warnings: it's possible that the -call may be trim-safe now, but as you change your code that may change and you may forget to -review all the suppressions. +`UnconditionalSuppressMessage` is like `SuppressMessage` but it is preserved into IL, so the +trimmer can see the suppression even after build and publish. `SuppressMessage` and `#pragma` +directives are only present in source, so they can't be used to silence warnings from the +trimmer. Be very careful when suppressing trim warnings: it's possible that the call may be +trim-compatible now, but as you change your code that may change and you may forget to review all +the suppressions. ### DynamicallyAccessedMembers `DynamicallyAccessedMembers` is usually about reflection. Unlike `RequiresUnreferencedCode`, -reflection can sometimes be understood by the trimmer, as long as it's annotated correctly. +reflection can sometimes be understood by the trimmer as long as it's annotated correctly. Let's take another look at the original example: ```C# @@ -114,7 +117,7 @@ foreach (var m in type.GetMethods()) ``` In the example above, the real problem is `Console.ReadLine()`. Because *any* type could -be read, the trimmer has no way to know if you need members on `System.Tuple` or `System.Guid` +be read, the trimmer has no way to know if you need methods on `System.Tuple` or `System.Guid` or any other type. On the other hand, if your code looked like, ```C# @@ -141,7 +144,7 @@ void M2(Type type) // parameter 'type' of method 'net6.Program.M2(Type)' does not have matching annotations. The // source value must declare at least the same requirements as those declared on the target // location it is assigned to. - var members = type.GetMethods(); + var methods = type.GetMethods(); ... } ``` @@ -161,24 +164,24 @@ void M1() void M2( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { - var members = type.GetMethods(); + var methods = type.GetMethods(); ... } ``` -Now the warning disappears, because the trimmer knows exactly which members to preserve, and on -which type to preserve them on. In general, this is the best way to deal with +Now the warning disappears, because the trimmer knows exactly which members to preserve, and +which type(s) to preserve them on. In general, this is the best way to deal with `DynamicallyAccessedMembers` warnings: add annotations so the trimmer knows what to preserve. As with `RequiresUnreferencedCode`, adding `RequiresUnreferencedCode` or `UnconditionalSuppressMessage` attributes also works, but none of these options make the code -safe to trim, while adding `DynamicallyAccessedMembers` does. +compatible with trimming, while adding `DynamicallyAccessedMembers` does. ## Conclusion This description should cover the most common situations you end up in while trimming your application. Over time we'll continue to improve the diagnostic experience and provide more streamlined -ways to make your code trim-safe. +ways to make your code trim-compatible. As we continue developing trimming we hope to see more code that's fully annotated, so users can trim with confidence. Because trimming involves the whole application, trimming is as much a From 06809366ad793173d22ba972448ddb53591ac051 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Apr 2021 10:30:50 -0700 Subject: [PATCH 06/10] Readability --- docs/fixing-warnings.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 98b37cca73d8..c7e61b51a1bc 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -2,10 +2,10 @@ # Trim warnings in .NET 6 In .NET Core 3.1 and 5.0 we introduced trimming as a new preview feature for self-contained .NET -core applications. Conceptually, the feature is very simple: when publishing the application, the +core applications. Conceptually the feature is very simple: when publishing the application the .NET SDK analyzes the entire application, including the .NET framework, and removes all unused code. In the time trimming has been in preview, we've learned that trimming is very powerful -- -it can reduce application size by half, or more. However, we've also learned about the +it can reduce application size by half or more. However, we've also learned about the difficulties in safely trimming applications. The most difficult question in trimming is what is unused, or more precisely, what is used. For @@ -29,11 +29,11 @@ It's very likely that this code could have worked before trimming (as long as th something known to exist in the target framework), but would probably produce a null reference exception after trimming (due to `Type.GetType` returning null). -This is a frustrating situation. Trimming often works just fine, but occasionally it can produce +This is a frustrating situation. Trimming often works just fine but occasionally it can produce breaking behavior, sometimes in rare code paths, and it can be very hard to trace down the cause. For .NET 6 we want to bring a new feature to trimming: trim warnings. Trim warnings happen -because the trimmer sees a call which may access other code in the app, but the trimmer can't +because the trimmer sees a call which may access other code in the app but the trimmer can't determine which code. This could mean that the trimmer would trim away code which is used at runtime. @@ -96,7 +96,7 @@ void TestMethod() `UnconditionalSuppressMessage` is like `SuppressMessage` but it is preserved into IL, so the trimmer can see the suppression even after build and publish. `SuppressMessage` and `#pragma` -directives are only present in source, so they can't be used to silence warnings from the +directives are only present in source so they can't be used to silence warnings from the trimmer. Be very careful when suppressing trim warnings: it's possible that the call may be trim-compatible now, but as you change your code that may change and you may forget to review all the suppressions. @@ -117,7 +117,7 @@ foreach (var m in type.GetMethods()) ``` In the example above, the real problem is `Console.ReadLine()`. Because *any* type could -be read, the trimmer has no way to know if you need methods on `System.Tuple` or `System.Guid` +be read the trimmer has no way to know if you need methods on `System.Tuple` or `System.Guid` or any other type. On the other hand, if your code looked like, ```C# @@ -154,7 +154,7 @@ methods, so annotation is needed to pass information upward, from the reflection (`GetMethods`) to the source of the `Type` (`typeof`). In the above example, the trimmer warning is stating that `GetMethods` requires the `PublicMethods` annotation on types, but the `type` variable doesn't have the same requirement. In other words, we need to pass the requirements from -`GetMethods` up to the caller i.e., +`GetMethods` up to the caller: ```C# void M1() From 4dfa391fa8c52cbe44582715d77d21ff85f023e5 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 12 Apr 2021 10:36:56 -0700 Subject: [PATCH 07/10] Add references to docs --- docs/fixing-warnings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index c7e61b51a1bc..ee61d4438ea7 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -50,7 +50,7 @@ There are two big categories of warnings which you will likely see: ### RequiresUnreferencedCode -`RequiresUnreferencedCode` is simple: it's an attribute that can be placed on methods to indicate +[RequiresUnreferencedCode](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.requiresunreferencedcodeattribute?view=net-5.0) is simple: it's an attribute that can be placed on methods to indicate that the method is not trim-compatible, meaning that it might use reflection or some other mechanism to access code that may be trimmed away. This attribute is used when it's not possible for the trimmer to understand what's necessary, and a blanket warning is needed. This would often @@ -103,7 +103,7 @@ the suppressions. ### DynamicallyAccessedMembers -`DynamicallyAccessedMembers` is usually about reflection. Unlike `RequiresUnreferencedCode`, +[DynamicallyAccessedMembers](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-5.0) is usually about reflection. Unlike `RequiresUnreferencedCode`, reflection can sometimes be understood by the trimmer as long as it's annotated correctly. Let's take another look at the original example: From 81c095a93ca95808e14087feae17c3025111aefa Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 12 Apr 2021 21:41:29 -0700 Subject: [PATCH 08/10] Update fixing-warnings.md --- docs/fixing-warnings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index ee61d4438ea7..5e8d81dbf88e 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -3,7 +3,7 @@ In .NET Core 3.1 and 5.0 we introduced trimming as a new preview feature for self-contained .NET core applications. Conceptually the feature is very simple: when publishing the application the -.NET SDK analyzes the entire application, including the .NET framework, and removes all unused +.NET SDK analyzes the entire application and removes all unused code. In the time trimming has been in preview, we've learned that trimming is very powerful -- it can reduce application size by half or more. However, we've also learned about the difficulties in safely trimming applications. From 54b8832b47e7cabc504317cea8a037bd8d8523ad Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Tue, 13 Apr 2021 12:55:19 -0700 Subject: [PATCH 09/10] Feedback --- docs/fixing-warnings.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 5e8d81dbf88e..28668e812d56 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -133,35 +133,40 @@ it can use flow analysis to determine that it needs to keep all public methods. `DynamicallyAccessMembers` come in? What happens if the reflection is split across methods? ```C# -void M1() +void Method1() { - M2(typeof(System.Tuple)); + Method2(typeof(System.Tuple)); } -void M2(Type type) +void Method2(Type type) { - // Trim analysis warning IL2070: net6.Program.M2(Type): 'this' argument does not satisfy - // 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. The - // parameter 'type' of method 'net6.Program.M2(Type)' does not have matching annotations. The - // source value must declare at least the same requirements as those declared on the target - // location it is assigned to. var methods = type.GetMethods(); ... } ``` -Now you see a warning. For performance and stability flow analysis isn't performed between +If you compile the above, now you see a warning: + +``` +Trim analysis warning IL2070: net6.Program.Method2(Type): 'this' argument does not satisfy +'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. The +parameter 'type' of method 'net6.Program.Method2(Type)' does not have matching annotations. The +source value must declare at least the same requirements as those declared on the target +location it is assigned to. +``` + +For performance and stability flow analysis isn't performed between methods, so annotation is needed to pass information upward, from the reflection call (`GetMethods`) to the source of the `Type` (`typeof`). In the above example, the trimmer warning -is stating that `GetMethods` requires the `PublicMethods` annotation on types, but the `type` +is saying that `GetMethods` requires the `PublicMethods` annotation on types, but the `type` variable doesn't have the same requirement. In other words, we need to pass the requirements from `GetMethods` up to the caller: ```C# -void M1() +void Method1() { - M2(typeof(System.Tuple)); + Method2(typeof(System.Tuple)); } -void M2( +void Method2( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { var methods = type.GetMethods(); @@ -173,15 +178,14 @@ Now the warning disappears, because the trimmer knows exactly which members to p which type(s) to preserve them on. In general, this is the best way to deal with `DynamicallyAccessedMembers` warnings: add annotations so the trimmer knows what to preserve. -As with `RequiresUnreferencedCode`, adding `RequiresUnreferencedCode` or +As with `RequiresUnreferencedCode` warnings, adding `RequiresUnreferencedCode` or `UnconditionalSuppressMessage` attributes also works, but none of these options make the code compatible with trimming, while adding `DynamicallyAccessedMembers` does. ## Conclusion This description should cover the most common situations you end up in while trimming your -application. Over time we'll continue to improve the diagnostic experience and provide more streamlined -ways to make your code trim-compatible. +application. Over time we'll continue to improve the diagnostic experience and tooling. As we continue developing trimming we hope to see more code that's fully annotated, so users can trim with confidence. Because trimming involves the whole application, trimming is as much a From f47a479fcb6fe96009fdef21b022cdfd2e8812fb Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 16 Apr 2021 15:06:52 -0700 Subject: [PATCH 10/10] Add link to previous blog post --- docs/fixing-warnings.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/fixing-warnings.md b/docs/fixing-warnings.md index 28668e812d56..17ed8d4363d1 100644 --- a/docs/fixing-warnings.md +++ b/docs/fixing-warnings.md @@ -1,9 +1,10 @@ # Trim warnings in .NET 6 -In .NET Core 3.1 and 5.0 we introduced trimming as a new preview feature for self-contained .NET -core applications. Conceptually the feature is very simple: when publishing the application the -.NET SDK analyzes the entire application and removes all unused +[In .NET Core 3.1 and 5.0 we introduced +trimming](https://devblogs.microsoft.com/dotnet/app-trimming-in-net-5/) as a new preview feature +for self-contained .NET core applications. Conceptually the feature is very simple: when +publishing the application the .NET SDK analyzes the entire application and removes all unused code. In the time trimming has been in preview, we've learned that trimming is very powerful -- it can reduce application size by half or more. However, we've also learned about the difficulties in safely trimming applications.