Pagina personale di:
Carlo Vecchio
appunti di C#, R, SQL Server, ASP.NET, algoritmi, numeri
Vai ai contenuti

C# - Come si fa: esempi vari, parte 2

C#

Verificare se il programma è già in esecuzione

  • Questo è un metodo, non so se sia il migliore, però funziona.
  • Aggiungere:

   using System.Diagnostics;

  • Aggiungere il metodo:

   private
bool VerificaProgrammaInEsecuzione()
   {
       
Process[] _Processi = Process.GetProcesses();
       
for (int i = 0; i < _Processi.Length; i++)
       {
           
string _ProcessNameCorrente = Process.GetCurrentProcess().ProcessName;
           
int _IdCorrente = Process.GetCurrentProcess().Id;
           
if (_Processi[i].ProcessName == _ProcessNameCorrente && _Processi[i].Id != _IdCorrente)
           {
               
return true;
           }
       }
       
return false;
   }

  • Utilizzare il metodo 'VerificaProgrammaInEsecuzione' per esempio nell'evento Shown() del form:

   if (VerificaProgrammaInEsecuzione())
   {
       
MessageBox.Show("Sono già in esecuzione! Ora esco...");
       
this.Close();
   }

Ricavare un IP da un URL
  • Il codice seguente permette di ricavare un indirizzo IP dato un indirizzo internet (cioè www.qualcosa).
  • Con la funzione data, si può allo stesso modo verificare su un certo sito è raggiungibile oppure non lo è.
  • Se un sito non è raggiungibile può esserci un problema nel sito stesso, oppure semplicemente non si è connessi ad Internet.
  • Innanzi tutto è necessario inserire tra gli using:

   using System.Net.NetworkInformation;

  • Questa funzione restituisce l'IP dato un URL.

   static
public string myFunction_GetIp(string url)
   {
       
string ip = "";
       
Ping pingSender = new Ping();

       
try
       {
           
PingReply reply = pingSender.Send(url);

           
if (reply.Status == IPStatus.Success)
               ip = reply.Address.ToString();   
// OK
           
else
               ip =
"";   // Not OK
       }
       
catch
       {
           ip =
"";
       }
       
return ip;
   }

  • Questo codice, messo nell'evento click di un bottone, verifica il funzionamento della funzione precedente.

   private
void btnTestPing_Click(object sender, EventArgs e)
   {
       
string s = myFunction_GetIp("www.google.com");
       
if (s == "")
           
MessageBox.Show("Sito non raggiungibile.");
       
else
           
MessageBox.Show("Sito raggiungibile all'indirizzo " + s + ".");
   }

Clonare un dictionary (o un oggetto generico)
  • Non tutti gli oggetti contengono il metodo Clone(), per il esempio il Dictionary non lo contiene.
  • Nel caso occorra copiare un Dictionary, si può utilizzare un overload del costruttore che accetta come parametro un altro Dictionary.
  • Il Dictionary che viene creato in questo modo contiene però una copia che in gergo è detta ‘shallow’, cioè superficiale.
  • I due Dictionary puntano alla stessa area di memoria. Questo vuol dire che se il Dictionary contiene come valore una classe, la copia contiene gli indirizzi degli oggetti della classe. Una modificata fatta su uno qualsiasi dei due Dictionary è quindi fatta anche nell’altro.
  • Spesso è necessario però avere un altro oggetto che occupi un’area di memoria distinta e che possa essere modificato senza modificare l’oggetto di partenza.
  • Per ottenere questo, si può naturalmente ciclare il Dictionary origine e per ogni elemento istanziare un nuovo oggetto, magari utilizzando il metodo Clone(), ma non sempre è facile o possibile, specialmente nel caso che la classe origine non sia clonabile.
  • Una soluzione più veloce fa uso della ‘serializzazione’. Serializzare un oggetto significa costruire un’area di memoria che ne contiene sia la struttura che i dati. Poi si può deserializzare la stessa area in un altro oggetto ottenendo così la copia voluta.
  • Ecco un esempio.

  • Si abbia la classe ‘Prova’ così definita, alla quale è stata aggiunta la possibilità di essere serializzata:

   [Serializable()]
   
public class Prova
   {
       
public int Proprieta1;
       
public string Proprietà2;

       
public Prova(int p1, string p2)
       {
           Proprieta1 = p1;
           Proprietà2 = p2;
       }
   }

  • La classe ‘Prova’ è volutamente semplificata (le proprietà pubbliche non sono incapsulate in 'get' e 'set'). Come si vede è presente il costruttore.
  • Il seguente metodo fa il clone di un oggetto, infatti ha come parametro un oggetto e restituisce un altro oggetto, utilizzando la serializzazione e la deserializzazione.

   using System.Runtime.Serialization.Formatters.Binary;

   
public static T DeepClone<T>(T obj)
   {
       
using (var ms = new MemoryStream())
       {
           
var formatter = new BinaryFormatter();
           formatter.Serialize(ms, obj);
           ms.Position = 0;
           
return (T)formatter.Deserialize(ms);
       }
  }


  • Ora definiamo un dictionary e aggiungiamo alcuni elementi ad esso.

   Dictionary<int, Prova> dicTest = new Dictionary<int, Prova>();
   dicTest.Add(1,
new Prova(10, "Primo oggetto"));
   dicTest.Add(2,
new Prova(20, "Secondo oggetto"));
   dicTest.Add(3,
new Prova(30, "Terzo oggetto"));

  • E infine eseguiamo la clonatura.

   Dictionary<int, Prova> dicClone = DeepClone(dicTest);

  • A questo punto i due Dictionary puntano ad aree di memoria differenti e qualsiasi modifica fatta su uno dei due non si ha nell’altro.
  • Attenzione che serializzare e deserializzare un oggetto è un processo relativamente lento e quindi in caso si richiedano alte prestazioni non è il metodo più adatto.

Generare un nuovo GUID
  • Gran parte del testo seguente è preso da Wikipedia, www.wikipedia.org.
  • Il GUID (Globally Unique Identifier, identificatore unico globale) è un numero pseudo-casuale usato nella programmazione software, per poter distinguere vari oggetti.
  • I GUID vengono utilizzati soprattutto nell'ambito del Component Object Model (COM) di Microsoft, tuttavia sono diffusi anche in altri sistemi software, quali Oracle e Novell.

Il GUID di Microsoft
  • Il GUID viene utilizzato per identificare in maniera univoca un determinato componente software COM; in questo modo due versioni (potenzialmente incompatibili) di uno stesso componente, anche con lo stesso nome, possono essere distinte tramite i loro codici GUID. Sebbene il GUID non garantisca un'effettiva unicità, il numero possibile di combinazioni (2^128) è talmente elevato da scongiurare una coincidenza tra due codici.

Struttura
  • Nella pratica un GUID è un numero di 128 bit (16 byte) in formato esadecimale quale può essere:

     3F 25 04 E0 4F 89 11 D3 9A 0C 03 05 E8 2C 33 01

  • I GUID vengono scritti tra parentesi graffe e divisi in cinque blocchi, uno di 4 byte, tre di 2 byte e uno di 6 byte. Nell'esempio precedente si avrebbe:

     {3F2504E0-4F89-11D3-9A0C-0305E82C3301}

  • La notazione è quindi una sequenza formata dai seguenti gruppi di caratteri, divisi da un trattino (-).
       - Gruppo 1 (8 caratteri)
       - Gruppo 2 (4 caratteri)
       - Gruppo 3 (4 caratteri)
       - Due elementi iniziali del gruppo 4 (4 caratteri)
       - Sei elementi rimanenti del gruppo 4 (12 caratteri)

Algoritmo
  • L'algoritmo specificato dalla OSF e utilizzato da Microsoft per la generazione dei GUID è stato oggetto di critica. Infatti tale algoritmo utilizzava come punto di partenza gli indirizzi MAC delle schede di rete degli utenti che lo generavano. Il vantaggio di questo metodo era l'assoluta certezza dell'univocità del GUID generato (perché non esistono due indirizzi MAC uguali), ma in questo modo era possibile risalire da un GUID al computer che l'aveva generato, con potenziali conseguenze sulla privacy e sulla sicurezza.
  • Dopo che venne alla luce questo problema, Microsoft modificò l'algoritmo (implementato nell'API UuidCreate) in modo che non dipendesse più dagli indirizzi MAC. È ancora possibile generare volontariamente GUID dipendenti dall'indirizzo MAC usando l'API UuidCreateSequential.
  • Per sapere se un GUID è stato generato con la prima o con la seconda versione dell'algoritmo, bisogna fare riferimento alla prima cifra del terzo blocco. Quindi, nell'esempio precedente:

{3F2504E0-4F89-11D3-9A0C-0305E82C3301}

  • Se questa cifra è un 1, come in questo caso, il codice è generato dalla prima versione; viceversa, se la cifra è un 4, è generato dalla seconda.
  • Con il codice seguente si estrae facilmente un GUID con la seconda versione dell'algoritmo.

   string _guid = "";
   
Guid obj = Guid.NewGuid();
   _guid = obj.ToString().ToUpper();
   
MessageBox.Show("New Guid is: " + _guid);

Crittografia con algoritmo MD5
  • L'algoritmo MD5 è una funzione hash crittografica realizzata da Ronald Rivest nel 1991 e standardizzata con la RFC 1321. [Definizione tratta da Wikipedia]
  • La funzione MD5 permette di codificare una stringa di lunghezza arbitraria e restituisce una stringa di 128 bit (oppure in esadecimale una stringa di 32 caratteri esadecimali).
  • La stringa ottenuta si dice 'Checksum' o 'MD5 Hash'.
  • Non è possibile fare il passaggio inverso, cioè risalire alla stringa originale data la stringa codificata.
  • Esistono però database con moltissime stringhe precalcolate, utilizzati normalmente da hacker. Per questo motivo è consigliato modificare la stringa da codificare aggiungendo per esempio qualcosa all'inizio o alla fine. In questo modo la stringa codificata probabilmente non comparirà nei database precalcolati permettendo una codifica più sicura.
  • La stringa codificata non è univoca, nel senso che possono esserci più stringhe di input che danno la stessa stringa di output.

  • C# permette la codifica di una stringa secondo l'algoritmo MD5 in modo molto semplice, utilizzando la classe MD5 presente in 'System.Security.Cryptography'.
  • Ecco un esempio.

   using System.Security.Cryptography;

   
string _Source = "CARLO";
   
using (MD5 _Md5Object = MD5.Create())
   {
       
string _Hash = GetMd5Hash(_Md5Object, _Source);
       
MessageBox
.Show(_Hash);
   }


  • E questa è la funzione 'GetMd5Hash()':

   static string GetMd5Hash(MD5 md5Object, string input)
   {
       // Converte la stringa di input in un array di byte, poi calcola l'hash.
       // La stringa di input è meglio modificarla per evitare attacchi con dizionari precalcolati.
       byte[] _Data = md5Object.ComputeHash(Encoding.UTF8.GetBytes(input));
 
       // Cicla tutti i byte e li converte in esadecimale.
       StringBuilder _Output = new StringBuilder();
       for (int i = 0; i < _Data.Length; i++)
       {
           _Output.Append(_Data[i].ToString("x2"));
       }
 
       return _Output.ToString();
   }

Nome della classe e del metodo con la Reflection
  • Per ricavare - dall'interno di un metodo - il nome della classe e del metodo stesso, occorre utilizzare la Reflection.
  • Ecco un semplice esempio:

   // Necessita di using System.Reflection.
   MethodBase method = System.Reflection.MethodBase.GetCurrentMethod();
   string methodName = method.Name;
   string className = method.ReflectedType.Name;

Parametri di configurazione nel file App.config
  • Si possono memorizzare dei parametri di configurazione del programma in un file di configurazione.
  • In fase di sviluppo, nell'IDE di Visual Studio, il file di configurazione è 'App.config'. Esso è normalmente presente nella cartella dell'applicazione. Se non lo fosse, cliccare col tasto destro nell'Esplora Soluzione, scegliere 'Add -> New Item... -> Application Configuration File'.
  • Attenzione che, una volta compilato, il file di configurazione porta il nome del file eseguibile con l'aggiunta di '.config', per esempio 'Progetto.exe.config'. È quindi quest'ultimo file che va distribuito con l'applicazione.
  • Si consideri il file 'App.config' con il parametro 'Parametro1' il cui valore sia 'Valore1'.
  • Ecco la parte del file con il parametro:
 
 <configuration>
   <appSettings>
     <add key="Parametro1" value="Valore1" />
   </appSettings>
 </configuration>

  • Come si vede il parametro è nella sezione '<appSettings>' ed è identificato dalla parola chiave 'key' mentre il valore che assume è identificato dalla parola chiave 'value'.
  • Il parametro lo si può rendere visibile a tutto il programma impostando una variabile statica nel file 'Program.cs'.
 
   public static string Parametro1 = "";

  • Per leggere dal file 'App.config' occorre utilizzare la classe 'ConfigurationManager'.
  • (1) Aggiungere tra i riferimenti la dll 'System.Configuration'.
  • (2) Aggiungere 'using System.Configuration'.
  • Ecco come leggere il parametro e impostare la variabile una volta, per esempio all'avvio del programma.
 
   Program.Parametro1 = ConfigurationManager.AppSettings.Get("Parametro1").Trim();

  • Ed ecco come utilizzare il parametro.
 
   Console.WriteLine(Program.Parametro1);

Estrarre tutti i menu da un MenuStrip
  • Definire le due funzioni seguenti, la prima estrae gli elementi del ‘menuStrip’; la seconda estrae ricorsivamente gli elementi di un sottomenu.

   private static List<ToolStripMenuItem> GetItemsFromMenu(MenuStrip menuStrip)
   {
       List<ToolStripMenuItem> myItems = new List<ToolStripMenuItem>();
       foreach (ToolStripMenuItem i in menuStrip.Items)
       {
           GetItemsFromSubmenu(i, myItems);
       }
       return myItems;
   }

   private static void GetItemsFromSubmenu(ToolStripMenuItem item, List<ToolStripMenuItem> items)
   {
       items.Add(item);
       foreach (ToolStripItem i in item.DropDownItems)
       {
           if (i is ToolStripMenuItem)
           {
               GetItemsFromSubmenu((ToolStripMenuItem)i, items);
           }
       }
   }

  • Utilizzare la funzione nel seguente modo.

   List<ToolStripMenuItem> myItems = GetItemsFromMenu(this.mnuMain);
   foreach (var item in myItems)
   {
       //MessageBox.Show(item.Text);
       Console.WriteLine("Text: {0}, Name: {1}", item.Text, item.Name);
   }
 
  • Credit: https://www.codeproject.com/tips/264690/how-to-iterate-recursive-through-all-menu-items-in.

Ricavare le informazioni sui drive installati
  • Il codice seguente permette di ricavare alcune informazioni sui drive installati sul PC dove è in esecuzione il programma.
  • Tra le informazioni che si ottengono si hanno: il nome, il tipo, la formattazione, lo spazio disponibile e lo spazio totale.
  • Necessita del Namespace: System.IO.

   DriveInfo[] allDrives = DriveInfo.GetDrives();
   foreach (DriveInfo d in allDrives)
   {
       Console.WriteLine("Drive Name: {0}", d.Name);
      Console.WriteLine(" Drive type: {0}", d.DriveType);
       if (d.IsReady == true)
       {
           Console.WriteLine("  Volume label: {0}", d.VolumeLabel);
           Console.WriteLine("  RootDirectory: {0}", d.RootDirectory);
           Console.WriteLine("  File system: {0}", d.DriveFormat);
           Console.WriteLine("  Available free space to current user: {0} bytes", d.AvailableFreeSpace);
           Console.WriteLine("  Total free space: {0} bytes", d.TotalFreeSpace);
           Console.WriteLine("  Total size: {0} bytes ", d.TotalSize);
       }
   }

  • Risultato ottenuto (sul mio PC):

Drive Name: C:\
 Drive type: Fixed
 Volume label: Windows8_OS
 RootDirectory: C:\
 File system: NTFS
 Available free space to current user: 63075024896 bytes
 Total free space: 63075024896 bytes
 Total size: 448919498752 bytes
Drive Name: D:\
 Drive type: Fixed
 Volume label: LENOVO
 RootDirectory: D:\
 File system: NTFS
 Available free space to current user: 23387115520 bytes
 Total free space: 23387115520 bytes
 Total size: 26839347200 bytes
Drive Name: E:\
 Drive type: CDRom

  • Credit: https://docs.microsoft.com/it-it/dotnet/api/system.io.driveinfo.

Eseguire un comando DOS (prompt dei comandi)
  • Di seguito un metodo per eseguire un comando DOS, cioè eseguire un comando dalla linea di comando e ottenere in una stringa il risultato del comando.
  • Si utilizza la classe 'Process' contenuta nel namespace 'System.Diagnostics'.
  • Ecco il metodo.

   public static string ExecuteCommandSync(object command)
   {
       try
       {
           // Crea il ProcessStartInfo e lancia "cmd" con "/c " come parametro.
           // "/c" indica di eseguire il comando successivo e di uscire.
           System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

           // Reindirizza lo standard output verso il Process.StandardOutput StreamReader.
           procStartInfo.RedirectStandardOutput = true;
           procStartInfo.UseShellExecute = false;
 
           // Non crea la finestra nera.
           procStartInfo.CreateNoWindow = true;
 
           // Ora crea il processo e lo lancia.
           System.Diagnostics.Process proc = new System.Diagnostics.Process();
           proc.StartInfo = procStartInfo;
           proc.Start();

           // Il risultato in una stringa.
           string result = proc.StandardOutput.ReadToEnd();

           // Ritorna.
           return result;
       }
       catch (Exception)
       {
           return null;
       }
   }

  • Ecco un esempio di utilizzo del metodo precedente.
  • Si vuole ricavare il 'Volume Serial Number' dell'Hard Disk. Un metodo per ottenerlo è quello di lanciare il comando 'vol' nel prompt dei comandi.
  • Nella finestra del prompt dei comandi, si può leggere una stringa del tipo:

Microsoft Windows [Versione 10.0.17134.706]
(c) 2018 Microsoft Corporation. Tutti i diritti sono riservati.

C:\Users\carlo>vol
Il volume nell'unità C è Windows8_OS
Numero di serie del volume: 68B4-073F

C:\Users\carlo>

  • In C#, si può utilizzare il seguente metodo che utilizza il metodo definito precedentemente ExecuteCommandSync.

   public string HardDiskVolumeSerialNumber()
   {
       string res = ExecuteCommandSync("vol");
       int position = res.LastIndexOf('-');
 
       if (position > 0)
       {
           return res.Substring(position - 4, 9);
       }
       else
       {
           return "";
       }
   }

  • Come si vede, il risultato del comando 'vol' viene assegnato alla variabile 'res'. In essa viene infine ricercato il carattere '-' per identificare in modo univoco il 'Volume Serial Number'.
  • Nota: questo è solo un esempio di utilizzo di ExecuteCommandSync; non è certamente il metodo migliore per determinare il Volume Serial Number.

Eseguire un metodo dato il nome
  • Normalmente per eseguire un metodo basta scrivere la sola istruzione con il nome del metodo stesso.
  • In certi casi ci si può trovare a dover eseguire uno o più metodi per i quali si hanno delle variabili di tipo stringa che ne contengono il nome.
  • Ci viene in aiuto il metodo Invoke() di MethodInfo, dalla classe System.Reflection.
  • Ecco un esempio di esecuzione di tre metodi a partire dal loro nome impostato come stringa.
  • Il primo esempio è l'esecuzione del metodo "Method1_WithoutArguments", senza parametri.
  • Il secondo esempio è l'esecuzione del metodo "Method2_WithArguments" con due parametri.
  • Il terzo esempio è l'esecuzione del metodo "Method3_WithReturnedValue" che restituisce anche un valore.

   private void Form1_Load(object sender, EventArgs e)
   {
       Type t = typeof(Form1);
       object classInstance = Activator.CreateInstance(t, null);

       t.GetMethod("Method1_WithoutArguments").Invoke(classInstance, null);
       t.GetMethod("Method2_WithArguments").Invoke(classInstance, new object[] { "Hello ", "World!" });
       var prod = t.GetMethod("Method3_WithReturnedValue").Invoke(classInstance, new object[] { 6, 7 });
   }

   public void Method1_WithoutArguments()
   {
       MessageBox.Show("Metodo 1");
   }

   public void Method2_WithArguments(string String1, string String2)
   {
       MessageBox.Show("Metodo 2 - Parametri " + String1 + String2);
   }

   public int Method3_WithReturnedValue(int Factor1, int Factor2)
   {
       MessageBox.Show("Metodo 3 - Parametri " + Factor1.ToString() + " " + Factor2.ToString());
       return Factor1 * Factor2;
   }

  • Gli esempi precedenti considerano i metodi nella stessa classe della Form1.
  • Nel caso che i metodi da eseguire siano in una classe diversa, il codice necessario è il seguente.
  • L'evento Form1_Load() è simile al precedente, cambia l'istruzione dove viene determinato il tipo 't'.

   private void Form1_Load(object sender, EventArgs e)
   {
       Type t = typeof(MyMethods);
       object classInstance = Activator.CreateInstance(t, null);

       t.GetMethod("Method1_WithoutArguments").Invoke(classInstance, null);
       t.GetMethod("Method2_WithArguments").Invoke(classInstance, new object[] { "Hello ", "World!" });
       var prod = t.GetMethod("Method3_WithReturnedValue").Invoke(classInstance, new object[] { 6, 7 });
   }

  • La classe 'MyMethods' contiene il codice con i metodi da eseguire.

   public class MyMethods
   {
       public void Method1_WithoutArguments()
       {
           MessageBox.Show("Metodo 1");
       }

       public void Method2_WithArguments(string String1, string String2)
       {
           MessageBox.Show("Metodo 2 - Parametri " + String1 + String2);
       }

       public int Method3_WithReturnedValue(int Factor1, int Factor2)
       {
           MessageBox.Show("Metodo 3 - Parametri " + Factor1.ToString() + " " + Factor2.ToString());
           return Factor1 * Factor2;
      }
   }

Aggiungere un Tooltip ad una Label
  • Non esiste una proprietà accessibile direttamente alle Label per impostarne un Tooltip.
  • L'esempio seguente ha solo una Form e la label 'label1'.
  • Definire a livello di Form il seguente oggetto:

   ToolTip ttpLabelMessage = new ToolTip();

  • Impostare nell'evento Form_Load() le seguenti proprietà. Non tutte sono necessarie, provare a sperimentare e ad aggiungerne altre non descritte qua.

   ttpLabelMessage.IsBalloon = true;
   ttpLabelMessage.ShowAlways = true;
   ttpLabelMessage.ToolTipIcon = ToolTipIcon.Info;
   ttpLabelMessage.ToolTipTitle = "Titolo";
   ttpLabelMessage.SetToolTip(label1, "Hello World!");

  • Ecco il risultato:

© 2022 Carlo Vecchio
Torna ai contenuti