<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Topics tagged with netframework]]></title><description><![CDATA[A list of topics that have been tagged with netframework]]></description><link>https://forum.androidiani.net/tags/netframework</link><generator>RSS for Node</generator><lastBuildDate>Fri, 01 May 2026 00:36:11 GMT</lastBuildDate><atom:link href="https://forum.androidiani.net/tags/netframework.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 23 Apr 2026 15:01:47 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Addio byte[]: allocazioni a costo zero in .NET Framework con  ReadOnlySpan]]></title><description><![CDATA[Uno dei pattern di ottimizzazione più semplici e meno conosciuti nel mondo .NET è la sostituzione dei campi static readonly byte[] con proprietà static ReadOnlySpan&lt;byte&gt;. Andrew Lock, noto per le sue analisi approfondite su ASP.NET Core e il runtime, ha pubblicato un articolo che conferma un dettaglio fondamentale: questa tecnica funziona anche su .NET Framework, basta il pacchetto NuGet System.Memory. Zero allocazioni, zero costo di startup, nessuna pressione sul garbage collector.Il problema: allocazioni “gratuite” che non lo sonoConsideriamo un pattern che troviamo in quasi tutte le librerie che manipolano dati binari: signature di file, magic number, header fissi, tabelle di lookup. Tipicamente si scrive:public static class MyStaticData {     private static readonly byte[] ByteField = new byte[] { 1, 2, 3, 4 }; }Sembra innocuo: un singolo array, allocato una volta sola al caricamento del tipo. Ma in un processo con migliaia di tipi simili — pensiamo a un parser di formati immagine, a una libreria di crittografia, a un framework web — queste allocazioni si sommano. Ogni array è un oggetto gestito: richiede header, richiede tracciamento GC, occupa spazio sulla Gen 2 (perché sopravvive per sempre) e aumenta i tempi di startup.La soluzione: ReadOnlySpan&lt;byte&gt; come proprietàLa trasformazione è quasi meccanica:public static class MyStaticData {     private static ReadOnlySpan&lt;byte&gt; ReadOnlySpanProp =&gt; new byte[] { 1, 2, 3, 4 }; }Sintatticamente sembra che stiamo allocando un array ogni volta che accediamo alla proprietà. In realtà è esattamente il contrario: il compilatore C# riconosce questo pattern e incorpora i byte direttamente nei metadati dell’assembly, costruendo lo span con un puntatore a quei dati. Non viene mai eseguito newarr.L’IL generato mostra chiaramente la magia:IL_0000: ldsflda      int32 '&lt;PrivateImplementationDetails&gt;'::'...' IL_0005: ldc.i4.4 IL_0006: newobj       instance void valuetype [System.Memory]System.ReadOnlySpan`1&lt;unsigned int8&gt;::.ctor(void*, int32) IL_000b: retI dati vivono in una sezione di sola lettura dell’assembly; lo span viene costruito on-the-fly con pointer + length. È essenzialmente gratuito.Letterali UTF-8: lo stesso trucco, più ergonomicoA partire da C# 11 (.NET 7), la stessa ottimizzazione si ottiene con i letterali UTF-8:private static ReadOnlySpan&lt;byte&gt; Utf8Hello =&gt; "Hello world"u8;Il suffisso u8 istruisce il compilatore a codificare la stringa direttamente in UTF-8 nell’assembly. Molto utile per header HTTP, prefissi di protocollo, marker di formato binari — tutti casi in cui storicamente si manteneva una byte[] statica generata da Encoding.UTF8.GetBytes.I vincoli da rispettareL’ottimizzazione non si applica in modo uniforme. Vale solo per i tipi a byte singolo:byte[]sbyte[]bool[]Per gli altri tipi primitivi (int, long, double…) entra in gioco l’endianness: su .NET 7 e successivi c’è RuntimeHelpers.CreateSpan&lt;T&gt;() che la gestisce in modo trasparente, ma su .NET Framework il compilatore emette codice che cache l’array in un campo statico alla prima chiamata. Ancora efficiente, ma non zero-alloc.Il secondo vincolo è che tutti i valori devono essere costanti a compile-time:// Anti-pattern: alloca a ogni accesso private static readonly byte One = 1; private static ReadOnlySpan&lt;byte&gt; Bad =&gt; new byte[] { One, 2, 3, 4 };Qui One è un campo, non una costante, quindi il compilatore deve costruire l’array a runtime. La differenza tra const byte e static readonly byte diventa improvvisamente importante.Il terzo vincolo è usare ReadOnlySpan&lt;T&gt;, mai Span&lt;T&gt;:// Sbagliato: alloca un array mutabile a ogni accesso private static Span&lt;byte&gt; MutSpan =&gt; new byte[] { 1, 2, 3, 4 };Uno Span&lt;byte&gt; potrebbe essere scritto, e modificare dati immutabili condivisi sarebbe catastrofico. Il compilatore quindi non applica l’ottimizzazione.Il supporto su .NET FrameworkQuesta è la parte più interessante: il trucco funziona su .NET Framework 4.6.2+ semplicemente referenziando il pacchetto System.Memory:&lt;ItemGroup&gt;   &lt;PackageReference Include="System.Memory" Version="4.6.3" /&gt; &lt;/ItemGroup&gt;La ragione è che l’ottimizzazione è una feature del compilatore, non del runtime: serve solo che ReadOnlySpan&lt;T&gt; esista come tipo, e il pacchetto System.Memory lo fornisce. Chi mantiene librerie multi-target può quindi applicare questa ottimizzazione senza creare codice condizionale #if NET6_0_OR_GREATER.Collection expressions: la rete di sicurezzaSu C# 12 e successivi le collection expressions offrono protezione a compile-time:// Compila e non alloca private static ReadOnlySpan&lt;byte&gt; Safe =&gt; [1, 2, 3, 4];  // Errore CS9203 — il compilatore rifiuta private static Span&lt;byte&gt; Dangerous =&gt; [1, 2, 3, 4];L’errore CS9203 è un salvavita: impedisce di assegnare una collection expression a un tipo Span&lt;T&gt; in contesti static, perché il risultato sarebbe condivisibile e mutabile. Su .NET Framework o su versioni di C# precedenti questa protezione non esiste, quindi serve attenzione in fase di code review.Quando applicarla nel codice realeLe candidate ideali sono costanti binarie che vivono in campi static readonly byte[]: magic number (PNG, ZIP, PDF), prefissi protocollari, tabelle di sostituzione, chiavi di test fisse, certificati embedded. Il refactoring è meccanico e non cambia l’API pubblica della classe se la visibility è private.Attenzione invece ai metodi che accettano byte[]: non possiamo passare uno ReadOnlySpan&lt;byte&gt; a un’API che richiede un array. In questi casi la scelta è tra riscrivere il consumer per accettare ReadOnlySpan&lt;byte&gt; (preferibile) o mantenere l’array tradizionale. Molte API del BCL sono già state aggiornate negli ultimi anni: Stream.Write, HashAlgorithm.ComputeHash, Encoding.GetString accettano tutti ReadOnlySpan&lt;byte&gt; in overload moderni.ConclusioneCambiare static readonly byte[] in static ReadOnlySpan&lt;byte&gt; =&gt; è uno di quei refactoring che riducono allocazioni e startup con una modifica locale a costo zero. Funziona anche su .NET Framework, quindi vale la pena considerarla durante la manutenzione di codice legacy — un punto che spesso sfugge perché l’ecosistema associa Span&lt;T&gt; esclusivamente a .NET moderno.Fonte: Removing byte[] allocations in .NET Framework using ReadOnlySpan&lt;T&gt; di Andrew Lock.]]></description><link>https://forum.androidiani.net/topic/9882bbb4-d010-4a5e-a02d-eca46c97ec7b/addio-byte-allocazioni-a-costo-zero-in-.net-framework-con-readonlyspan</link><guid isPermaLink="true">https://forum.androidiani.net/topic/9882bbb4-d010-4a5e-a02d-eca46c97ec7b/addio-byte-allocazioni-a-costo-zero-in-.net-framework-con-readonlyspan</guid><dc:creator><![CDATA[blog@spcnet.it]]></dc:creator><pubDate>Thu, 23 Apr 2026 15:01:47 GMT</pubDate></item></channel></rss>