A Quick Guide for C# (CSharp) Programmers
In questa breve guida, esploreremo alcuni utili consigli per programmatori C#: l'utilizzo dei log con Log4NET, l'utilizzo di JSON, alcune funzioni utili per lavorare con i file, alcune conversioni utili e la conversione di una stringa in un valore booleano, gestione dei numeri random, gestione degli enumerati, usare i TRACE, etc.
LOGGING
Ipotesi: abbiamo un programma C# e vogliamo loggare determinate informazioni. Quale libreria conviene usare? In questo ambiente la più usata è Log4NET (priprio come in Java abbiamo la Log4J). Vediamo qualche piccolo trucco/consiglio per usare al meglio le funzionalità di logging di Log4NET.
Come inserire il log nella nostra classe? Il miglior metodo è:
private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Così facendo si dichiara nella nostra classe la variabile statica da usare per loggare.
Notare come con il codice System.Reflection.MethodBase.GetCurrentMethod().DeclaringType si prende il nome della classe in automatico (utilissima info da inserire nel file di log). Questo ha l'indubbio vantaggio di riutilizzare il codice al volo senza dover cambiare nulla.
Come fare il log nei nostri metodi? Io uso queste due istruzioni all'inizio e alla fine dei metodi più importanti:
string logPrefix = $"{MethodBase.GetCurrentMethod().DeclaringType.ToString()}.{MethodBase.GetCurrentMethod().Name}";
Logger.Info($"{logPrefix} - Method Begin");
...
Logger.Info($"{logPrefix} - Method End");
Anche qui, la motivazione di questo codice è quella di prendere in automatico il nome della classe con il suo namespace ed il nome del metodo.
Vediamo come loggare una eccezione compresa eventualmente la sua eccezione interna:
try{
...
}
catch(Exception ex)
{
string errorMsg = ex.Message;
if (ex.InnerException != null && !string.IsNullOrEmpty(ex.InnerException.Message))
{
errorMsg += ex.InnerException.Message;
}
Logger.Error(MethodBase.GetCurrentMethod().DeclaringType, errorMsg, ex);
throw;
}
I Livelli da usare nella configurazione dei log sono i seguenti:
All (Log everything)
Debug
Info
Warn
Error
Fatal
Off (Don’t log anything)
Cosa fare se log4net non riesce la loggare?
Questa libreria se incontra qualche problema non blocca il programma ma semplicemente non logga nulla. Basta agire nel file web.config ed inserire le seguenti configurazioni:
<configuration>
<appSettings>
<add key="log4net.Internal.Debug" value="true"/>
</appSettings>
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add
name="textWriterTraceListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="log4net.txt" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
La prima configurazione abilita la libreria log4net all'internal logging. Il tag system diagnostic abilita la scrittura del trace, altrimenti il log interno della libreria log4net non si vedrebbe.
Il file log4net.txt verrà scritto allo stesso livello del file web.config della nostra applicazione.
JSON
Ipotesi: abbiamo un programma C# e vogliamo gestire i msg JSON.
La libreria più usata è quella della Newtonsoft che può tranquillamente essere installata nella nostra solution tramite NuGet.
Come creare un messaggio JSON? Ecco i semplici passi
//Stringa che conterrà il nostro msg JSON
string jsonMsg = string.Empty;
JObject msgJson = JObject.FromObject(new { mioTag = "mioValore" });
//La seguente istruzione ci dà un msg JSON sotto forma di stringa e senza formattazione
jsonMsg = JsonConvert.SerializeObject(msgJson, Newtonsoft.Json.Formatting.None);
StringContent jsonContent = new StringContent(jsonMsg, Encoding.UTF8, "application/json");
Vediamo ora come convertire una stringa XML in msg JSON:
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
doc.LoadXml(myStringXML);
string jsonMsg = JsonConvert.SerializeXmlNode(doc);
Sempre per l'esempio appena visto possiamo anche fare:
string xmljson = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.None, true);
Ovvero gli diciamo di non usare alcuna formattazione e di non inserire la root del nostro XML.
Come ciclare su un JArray (JSON Array)?
JArray jarr = (JArray)response.Response["fields"];
foreach (JObject contentTmp in jarr.Children<JObject>())
{
foreach (JProperty prop in contentTmp.Properties())
{
string tempValue = prop.Value.ToString();
//here more code in order to save in a database
}
}
Come scrivere in una colonna del database una string formattata come JSON?
E' molto utile avere in una colonna NVARCHAR(max) del nostro database un contenuto in formato JSON. Vediamo come fare:
var objJson = new JObject();
objJson["miaChiave"] = System.Guid.NewGuid();
string testJson = JsonConvert.SerializeObject(objJson, Formatting.None);
Si crea prima un oggetto JObject fornito dalla libreria JSon. Su questo oggetto si possono inserire tutte le coppia "chiave,valore" che si vogliono e poi lo si formatta/serializza in maniera da renderlo compatibile con una stringa. La stringa risultante testJson la si mette nel campo del database.
Conversione da stringa JSon ad oggetto
Vediamo come avere un oggetto a partire dalla sua rappresentazione in JSon:
MiaClasse miaClasseObj = JsonConvert.DeserializeObject<MiaClasse>(stringaJson);
Conversione da oggetto a string JSON
Vediamo come convertire un oggetto nella sua rappresentazione JSon:
string jsonVersion = JsonConvert.SerializeObject(MyObject, Formatting.None);
Selezionare un Token in una string JSon
string myValue = JsonConvert.DeserializeObject<JObject>(valueJson).SelectToken("MyToken").ToString();
FILE
Vediamo come convertire una immagine in un ByteArray e poi in string a base64 (utilissima tecnica ad esempio per chiamate REST)
Vediamo come avere una stringa Base64 a partire da una immagine:
byte[] imageArray = System.IO.File.ReadAllBytes(@"path della immagine");
string base64ImageRepresentation = Convert.ToBase64String(imageArray);
Vediamo il processo inverso da stringa a File.
Vediamo come scrivere una stringa in un file:
using (StreamWriter outputFile = new StreamWriter(@"path del file txt"))
{
outputFile.WriteLine(base64ImageRepresentation);
}
Conversione da stringa base64 a byte[]:
byte[] image = Convert.FromBase64String(base64ImageRepresentation);
Da byte[] a file con sovrascrittura
Vediamo come a partire da un array di byte possiamo scrivere il file su file system tenendo conto che il file possa esserci già e quindi va sovrascritto (in questo caso se c'è prima lo cancelliamo):
byte[] myByte = ...;
if (File.Exists(@"C:\myPath\myFile.png")) {
File.Delete(@"C:\myPath\myFile.png");
}
System.IO.FileStream fileStream = new System.IO.FileStream(@"C:\myPath\myFile.png", System.IO.FileMode.Create, System.IO.FileAccess.Write);
fileStream.Write(myByte, 0, myByte.Length);
fileStream.Close();
CONVERSIONI
Da string a Double
Il nostro scopo è convertire una string in un double tenendo conto che il numero reale possa avere il punto o la virgola come separtore:
double y;
try
{
y = double.Parse(myStringWithDouble, CultureInfo.InvariantCulture);
} catch (Exception) {
y = 0.0;
}
Se qualcosa va storto si mette come valore di default 0.0.
Boolean.TryParse()
Vediamo come gestire al meglio la conversione di una stringa in booleano.
Il miglior codice da usare è il seguente:
bool mioValoreBool = false;
string valoreStrDaconvertire = .....;
if (!Boolean.TryParse(valoreStrDaconvertire, out mioValoreBool))
{
log.Info("Conversione in bool non riuscita.");
mioValoreBool = false;
}
La variabile mioValoreBool conterrà la conversione in booleano del valore in stringa valoreStrDaconvertire.
Se la conversione non riesce la funzione ritorna False e questo valore deve essere gestito esplicitamente altrimenti in fase di analisi statica del codice si ottiene il warning seguente:
CA1806 Do not ignore method results
Numeri Random
Vediamo come generare in C# dei numeri random:
Random rnd = new Random();
rnd.Next(1,10);
Questa istruzione genera numeri random nel range 1,9.
Quindi il limite superiore è incluso mentre quello superiore è escluso.
Una versione più corretta sarebbe la seguente:
Random rnd = new Random(DateTime.Now.Millisecond);
rnd.Next(1,10)
In questo caso passando gli attuali millisecondi si usa un seme variabile e quindi la generazione dei numeri random è acora più casuale.
Enum
From String to Enum
Vediamo come convertire una stringa in un Enum:
public MyEnum GetStringToEnumValue()
{
string myStringToEnum = "some value that represent an enum";
if(Enum.TryParse(myStringToEnum, out MyEnum myStatus))
{
return myStatus;
}
throw new Exception("...");
}
Abilitare il TRACE per le comunicazioni
Questa configurazione serve per abilitare la tracciatura dei messaggi di rete in un'applicazione basata su .NET. La tracciatura verrà registrata in un file di testo chiamato "trace.log" e verrà effettuata solo per il protocollo di rete. La tracciatura verrà effettuata ogni volta che viene effettuata una richiesta di rete e verrà utilizzata per identificare eventuali problemi di rete o di prestazioni. Inoltre, l'opzione "autoflush" garantisce che i dati di tracciatura vengano scritti sul disco in modo tempestivo, anziché mantenuti in un buffer.
La seguente configurazione va messa nel tag configuration del file web.config.
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="System.Net" maxdatasize="9999" tracemode="protocolonly">
<listeners>
<add name="TraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
</switches>
</system.diagnostics>
Un altro modo per abilittare il TRACE è il seguente:
<!-- De-commentare per usare il TRACE per le comunicazioni -->
<system.diagnostics>
<sources>
<source name="System.IdentityModel" switchValue="Verbose" logKnownPii="true">
<listeners>
<add name="xml" />
</listeners>
</source>
<!-- Log all messages in the 'Messages' tab of SvcTraceViewer. -->
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="xml" />
</listeners>
</source>
<!-- ActivityTracing and propogateActivity are used to flesh out the
'Activities' tab in SvcTraceViewer to aid debugging. -->
<source name="System.ServiceModel" switchValue="Error, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="xml" />
</listeners>
</source>
<!-- This records Microsoft.IdentityModel generated traces, including
exceptions thrown from the framework. -->
<source name="Microsoft.IdentityModel" switchValue="Warning">
<listeners>
<add name="xml" />
</listeners>
</source>
</sources>
<sharedListeners>
<add name="xml"
type="System.Diagnostics.XmlWriterTraceListener" initializeData="E:\trace.svclog" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
<!-- END TRACE per le comunicazioni -->
<system.serviceModel>
<diagnostics>
<!-- log all messages sent/received at the transport/service model level -->
<messageLogging logEntireMessage="true"
maxMessagesToLog="1000"
maxSizeOfMessageToLog="69905067"
logMessagesAtServiceLevel="true"
logMalformedMessages="true"
logMessagesAtTransportLevel="true" />
</diagnostics>
</system.serviceModel>
Un altro esempio ancora è:
<system.diagnostics>
<sources>
<source name="System.Net" tracemode="includehex" maxdatasize="1024">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Cache">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Http">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.Sockets">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
<source name="System.Net.WebSockets">
<listeners>
<add name="System.Net"/>
</listeners>
</source>
</sources>
<switches>
<add name="System.Net" value="Verbose"/>
<add name="System.Net.Cache" value="Verbose"/>
<add name="System.Net.Http" value="Verbose"/>
<add name="System.Net.Sockets" value="Verbose"/>
<add name="System.Net.WebSockets" value="Verbose"/>
</switches>
<sharedListeners>
<add name="System.Net" type="System.Diagnostics.TextWriterTraceListener" initializeData="network.log"
traceOutputOptions="ProcessId, DateTime" />
</sharedListeners>
<trace autoflush="true"/>
</system.diagnostics>
Scegliete quello che più fa per voi!!!
Abilitare il TRACE per le entityFramework
Un sistema analogo come quello visto sopra può essere usato per Entity Framework.
Questa configurazione serve per configurare Entity Framework, un framework per l'accesso ai dati per le applicazioni .NET.
La proprietà defaultConnectionFactory specifica il tipo di fabbrica di connessioni di default da utilizzare per la creazione di connessioni a un database.
La sezione providers elenca i provider di database supportati da Entity Framework. In questo caso, viene specificato che Entity Framework utilizzerà il provider SQL Server per la comunicazione con il database.
La sezione interceptors specifica un'intercettazione della tracciatura del database. L'intercettazione registrerà tutte le query e le operazioni sul database eseguite tramite Entity Framework in un file di testo chiamato "LogOutput.txt" nella cartella "C:\temp". L'opzione "true" specifica che la tracciatura deve essere effettuata per tutte le query. Questa configurazione può essere utilizzata per diagnosticare eventuali problemi di prestazioni del database o per comprendere meglio le query eseguite sul database tramite Entity Framework.
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"/>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
</providers>
<interceptors>
<interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework">
<parameters>
<parameter value="C:\temp\LogOutput.txt"/>
<parameter value="true" type="System.Boolean"/>
</parameters>
</interceptor>
</interceptors>
</entityFramework>
Abilitare via codice il controllo delle performance
Con il seguente codice si può controllare il tempo impiegato nell'esecuzione del codice stesso:
Stopwatch sw = new Stopwatch();
sw.Start();
Test1();
sw.Stop();
Console.WriteLine("Time Taken-->{0}",sw.ElapsedMilliseconds);
La riga di codice "Stopwatch sw = new Stopwatch();" crea un nuovo oggetto Stopwatch chiamato "sw".
La riga successiva "sw.Start();" avvia il cronometro.
La riga "Test1();" esegue un metodo chiamato Test1().
La riga "sw.Stop();" ferma il cronometro.
L'ultima riga di codice "Console.WriteLine("Time Taken-->{0}",sw.ElapsedMilliseconds);" stampa il tempo trascorso in millisecondi dall'avvio.
In generale, come misurare le performance di una parte di codice C#?
In C#, ci sono diversi modi per controllare le prestazioni di una parte di codice:
Utilizzando l'oggetto Stopwatch: come menzionato in precedenza, è possibile creare un oggetto Stopwatch, avviarlo prima dell'esecuzione della parte di codice di cui si desidera misurare le prestazioni, fermare il cronometro una volta che la parte di codice è stata eseguita e quindi stampare il tempo trascorso sulla console.
Utilizzando il profiler di performance di Visual Studio: il profiler di performance di Visual Studio consente di analizzare in modo dettagliato le prestazioni del codice, inclusi i tempi di esecuzione dei metodi, l'utilizzo della CPU e della memoria.
Utilizzando il cronometro di sistema: il cronometro di sistema di .NET fornisce un modo per misurare il tempo di esecuzione di una parte di codice utilizzando la classe System.Diagnostics.Stopwatch.
Utilizzando il log delle prestazioni: è possibile creare il proprio sistema di log delle prestazioni per registrare le informazioni sui tempi di esecuzione di una parte di codice in un file o in una base di dati, in modo da poter analizzare i dati in un secondo momento.
In generale, l'utilizzo dell'oggetto Stopwatch è un modo semplice per misurare le prestazioni di una parte di codice, mentre il profiler di performance di Visual Studio fornisce una visione più dettagliata delle prestazioni del codice.
Buon lavoro!