Visual Studio 2005 Snippet
a cura di Sabrina Cosolo & Rudy Azzan (requisiti: conoscenza di base di Visual Studio .Net)

Premessa
In Visual Studio 2005, uno degli strumenti più potenti per l'aiuto alla stesura del codice in modo rapido e soprattutto corretto, è il Refactoring, ovvero uno strumento che si occupa di aiutarci a non dimenticare qualche parte del codice, quando rinominiamo una classe, una variabile, un qualsiasi oggetto all'interno del codice, modificando automaticamente, o comunque chiedendoci conferma per poi modificare automaticamente, i riferimenti all'oggetto che abbiamo deciso di rinominare.

Ma il refactoring non è il solo strumento utile: infatti, molto spesso, classi e oggetti simili devono essere costruiti in un programma e non sempre è possibile utilizzare OOP per creare oggetti generici da ereditare così malleabili da evitare questo tipo di lavoro. In questi casi, il famigerato copia-e-incolla diviene necessario, ma purtroppo a volte è pericoloso quasi quanto un GoTo (nota espressione blasfema in tutti i linguaggi di programmazione.:))

Per ovviare al problema, coloro che hanno costruito Visual Studio, che posso presumere abbiano i nostri stessi problemi, hanno, soprattutto in questa versione 2005, dialogato con i Programmatori per sapere cosa volessero in un sistema di sviluppo ideale, molto prima dell'uscita della Beta 2. Uno dei risultati è che hanno creato una cosa chiamata snippet. Che cos'è uno snippet? la traduzione dall'inglese della parola ci dà qualche suggerimento: sul Ragazzini alla parola snippet corrisponde la traduzione frammento; pezzetto; ritaglio. Infatti si tratta di una porzione di codice, ma gli snippet non sono semplicemente dei repository di copia-e-incolla, perché nelle porzioni di codice di uno snippet, è possibile inserire una serie di placeholder (segnaposto), che ci permettono di evidenziare, per poi essere obbligati da Visual Studio a sostituire, i valori variabili necessari a rendere il nostro pezzo di codice perfettamente funzionante in base alla classe che stiamo costruendo.

Rudy, che cito come coautore, è stato il primo (di noi due) a cercare di capire come funzionano, per utilizzarli al posto delle macro, unico mezzo simile esistente in Visual Studio 2003 per effettuare lo stesso tipo di lavoro, ha generato i nostri primi snippet poco dopo aver iniziato a lavorare con me con la Beta 2 lo scorso anno e insieme abbiamo poi aumentato di numero questi snippet, per agevolare e soprattutto standardizzare la stesura di codice simile all'interno delle nostre classi. Ogni settimana uno o due nuovi snippet si aggiungono a quelli che abbiamo già prodotto ed utilizziamo. Oltre ai nostri snippet personalizzati, abbiamo modificato anche parecchi di quelli di sistema, per fare in modo che oggetti come le proprietà o costrutti quali i try catch siano generati con il nostro standard, anziché con quello di Visual Studio.

Ho deciso di scrivere questo articolo perché devo predisporre per un collega la traduzione in VB di alcuni dei miei snippet, e mi sono accorta che nei file VB gli snippet non funzionano proprio come in C#. Così, scrivendo questo articolo, prenderò due piccioni con una fava: uno è spiegare per quanto possibile in modo semplice quanto sia facile creare degli snippet per aiutarci nel lavoro quotidiano, l'altro è imparare quali sono le differenze fra gli snippet C# e quelli VB, in modo da poter argomentare con cognizione di causa al mio caro collega Doctor Bit quanti punti C# può dare a VB :D :D :D (ovviamente scherzo e Alberto lo sa...)

Vediamo uno snippet
Andiamo allora ad incominciare, in modo scientifico partendo dall'osservare uno snippet.
Ho preso uno degli snippet standard di VB e C#, che genera un costrutto Try Catch all'interno di un file VB o di un file C#:

VB
 
<?xml version="1.0" encoding="UTF-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Try...Catch...End Try Statement</Title>
      <Author>Microsoft Corporation</Author>
      <Description>Inserts a Try...Catch...End Try statement.</Description>
      <Shortcut>TryC</Shortcut>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object>
          <ID>ExceptionType</ID>
          <Type>Exception</Type>
          <ToolTip>Replace with the specific exception type you want to catch.</ToolTip>
          <Default>ApplicationException</Default>
        </Object>
      </Declarations>
      <Code Language="VB" Kind="method body">
<![CDATA[Try
Catch ex As $ExceptionType$
End Try]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
C#
 
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
   <CodeSnippet Format="1.0.0">
     <Header>
       <Title>try</Title>
       <Shortcut>try</Shortcut>
       <Description>Code snippet for try catch</Description>
       <Author>Rudy A.</Author>
       <SnippetTypes>
         <SnippetType>Expansion</SnippetType>
         <SnippetType>SurroundsWith</SnippetType>
       </SnippetTypes>
     </Header>
     <Snippet>
       <Declarations>
         <Object>
           <ID>ExceptionType</ID>
           <Type>Exception</Type>
           <ToolTip>Replace with the specific exception type you want to catch.</ToolTip>
           <Default>ApplicationException</Default>
         </Object>
       </Declarations>
       <Code Language="csharp">
<![CDATA[
try
    {
        $end$$selected$
    }
    catch ($ExceptionType$ ex)
    {
        throw new ApplicationException(" " + mClassName + "."
            + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
    }
]]>
       </Code>
     </Snippet>
   </CodeSnippet>
</CodeSnippets>

Come potete notare, quello C# è già modificato secondo lo standard usuale di gestione delle eccezioni che io utilizzo, e che troverete in tutti gli articoli che ho scritto (e scriverò) relativi a Visual Studio. Per prima cosa, cerchiamo di comprendere quello che abbiamo davanti, poi modificheremo anche quello per VB.

Intanto, per i pochi che ancora non avessero avuto a che fare con XML, posso presentarvi due bellissimi esempi di file XML ben formato, quindi la prima cosa da sapere è che uno Snippet è un File XML. Non solo, lo snippet è un file XML ben formato che risponde ad uno schema ben preciso.
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

Questa frase XML all'interno del nostro file .snippet ci dice che c'è uno schema che definisce come è fatto uno snippet e presumo che, se provassimo a seguire l'URL, probabilmente troveremmo un documento che ci indica quali sono le caratteristiche che un file XML deve avere per essere uno snippet. Il fatto che segua uno schema, significa che probabilmente nelle prossime versioni di Visual Studio potrà essere esteso ulteriormente per essere migliorato.

Quello che possiamo notare osservando il documento è che il root tag dello snippet è <CodeSnippets>, al cui interno c'è un <CodeSnippet>, che indica invece a quale versione del parser Visual Studio risponde (1.0.0), probabilmente nelle versioni future sarà possibile implementare varie versioni di uno snippet funzionanti su diverse versioni del parser. Se scendiamo ancora di un livello nella gerarchia XML possiamo vedere che <CodeSnippet> contiene altri due tag, <Header> e <Snippet>, ovvero Intestazione e Snippet vero e proprio. Prima di andare a descrivere più dettagliatamente il tutto, vi invito a cercare dove si trova la porzione di codice VB o C#: ben poca cosa rispetto al resto dello snippet. Vediamo un po' di addentrarci nel dettaglio e capire come è fatto e come manipolare uno snippet e soprattutto dove metterlo per poterlo utilizzare.

Dove si trovano gli snippet?

Come possiamo vedere, gli snippet di sistema, o meglio di Default, forniti da Visual Studio .NET sono situati nella cartella %ProgramFiles%\Microsoft Visual Studio 8\VC#\Snippets oppure ...\VB\Snippets. Sotto a questa cartella c'è la cartella relativa alla lingua che nel caso dell'immagine è 1033 perché il mio Visual Studio è in Inglese, per l'italiano dovrebbe essere 1040. Sotto alla cartella della lingua, le sottocartelle degli snippet suddivisi per categoria nel caso di Visual Basic, inseriti in modo più stringato in C#, ma la disposizione credo dipenda da come chi li ha creati ha preferito inserirli. Devo dire però che in VB vi sono una maggiore quantità di snippet, anche se il loro uso è meno immediato, a causa del metodo in cui Intellisense lavora nei due linguaggi.

Una volta stabilito dove si trovano gli snippet di default, notate bene che, quando si modificano gli snippet standard è opportuno, se non necessario, fare una copia degli snippet modificati e salvarli su una cartella del nostro disco dati, infatti una reinstallazione o un'aggiornamento di Visual Studio andranno a sovrascrivere tutti gli snippet standard, anche quelli modificati. Se qualcuno si chiede allora perché li modifichiamo, posso rispondere in due modi, siamo masochisti e ci piace fare le cose più volte, oppure più semplicemente, sapere che ci basta scrivere try e battere invio, invece di inventare un nuovo nome per il nostro try catch personalizzato, risulta essere molto più immediato.

Vediamo invece come possiamo predisporre una o più cartelle ove riporre gli snippet personalizzati che costruiremo di sana pianta e desideriamo utilizzare all'interno di Visual Studio. Per farlo, vi invito ad aprire Visual Studio e provare a premere <ctrl>+<k> e, tenendo ancora premuto <ctrl>, premere <b>. Vi apparirà la finestra qui sotto:

Da questa Dialog box, utilizzando il comando Add... possiamo andare a indicare a Visual Studio quali cartelle contengono i nostri snippet personali. Premiamo quindi Add e selezioniamo la cartella dove abbiamo già messo o dove metteremo i nostri snippet personali:

Una volta selezionata o creata la cartella, premiamo il tasto Open e la cartella sarà aggiunta, assieme a tutte le sottocartelle che eventualmente contenesse, alle cartelle dove sono cercati gli Snippet da parte di Visual Studio.

Questa è la lista delle cartelle di Visual Basic, con la cartella da noi aggiunta (Vb) inserita nella treeview, precisiamo che la tree non rappresenta una tree in un file system ma un insieme che Visual Studio ricostruisce unendo le proprie cartelle alle cartelle da noi aggiunte.

La stessa struttura, possiamo trovarla in C# selezionando il linguaggio dalla combobox in testa alla dialog.

Anche se, come possiamo notare, anche in questo caso la suddivisione è molto più stringata.

La struttura di uno snippet in dettaglio

VB e C#
 
<?xml version="1.0" encoding="UTF-8"?>

Dichiarazione che serve a indicare che il file è un file XML, come vediamo è identica (come altre parti) per VB e C#.

VB e C#
 
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  ...
</CodeSnippets>

Tag Root dello snippet, che contiene tutti i componenti dello stesso e indica qual è lo schema che servirà a validare questo file XML; infatti, se provate ad inserire un tag non valido in mezzo allo snippet editandolo con Visual Studio, Visual Studio vi avviserà del fatto ed evidenzierà un errore. Inoltre vi darà l'Intellisense per alcune delle cose contenute nello snippet.

VB e C#
 
  <CodeSnippet Format="1.0.0">
    ...
  </CodeSnippet>

Corpo dello snippet, indica la versione con cui è stato predisposto, anche questo è un tag contenitore, e contiene al momento due Tag, <Header> e <Snippet>.

VB e C#
 
    <Header>
      ...
    </Header>
    <Snippet>
      ...
    </Snippet>

Tra i tag Header, come dice la parola stessa, sta l'intestazione dello snippet, che contiene quanto serve a identificare e usare lo snippet, oltre ad alcuni comandi specifici e generali per lo snippet stesso:

VB
 
      <Title>Try...Catch...End Try Statement</Title>
      <Description>Inserts a Try...Catch...End Try statement.</Description>
      <Author>Sabrina C.</Author>
      <Shortcut>TryC</Shortcut>
C#
 
      <Title>try</Title>
      <Description>Code snippet for try catch</Description>
      <Author>Rudy A.</Author>
      <Shortcut>try</Shortcut>

Contenuto comune a VB e C# dell'intestazione dello snippet: Titolo, Descrizione, Autore. Attenti a scrivere correttamente titolo e descrizione quando duplicate uno snippet, perché Visual Studio li usa entrambi nell'Intellisense. Il tag più importante fra questi è <Shortcut>, perché la parola inserita in questo tag è quella che, una volta digitata nel codice, alla pressione del tasto <Tab> in VB o del tasto <Enter> in C# fa partire l'esecuzione dello snippet; pertanto assicuriamoci di usare parole diverse per tutti gli snippet, e magari un prefisso per quelli da noi prodotti. Così da non rischiare di usare una parola già usata dagli snippet standard. Come vediamo, in VB lo shortcut utilizzato è TryC anzichè Try, perché lo statement Try in VB ha già quello che possiamo definire uno snippet interno che genera il classico try catch end try, mentre invece in C# è tutto a carico del programmatore, pertanto lo snippet è scatenato direttamente dallo statement C# try ed evita a noi maniaci delle graffe un bel po' di lavoro.

VB
 
Vb non supporta questo tag anche se è valido se inserito in uno snippet,
Infatti, in VB, nel menu di inserimento snippet non esiste la voce 
    SurroundsWith...
C#
 
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
        <SnippetType>SurroundsWith</SnippetType>
      </SnippetTypes>

Arriviamo alla prima vera differenza, questo statement dello Header non è supportato da Visual Basic in quanto al suo interno non è prevista l'opzione di inserimento snippet Surround With, che invece in C# è spesso utile, perché aggiungere un Try Catch dopo aver già scritto una serie di righe di codice può essere noioso, mentre questa opzione permette di selezionare una porzione di codice ed indicare all'editor di "Circondarla" con lo snippet selezionato. inserendo quindi quanto selezionato all'interno del codice dello snippet, in un punto indicato da un placeholder, come vedremo in seguito nel tag che contiene il codice.

Tra i tag <Snippet> sono contenuti i tag fondamentali che compongono uno snippet.

VB
 
      <Imports>
        <Import>
          <Namespace>System</Namespace>
        </Import>
      </Imports>
C#
 
In questo caso è C# a non supportare questo tag,
in quanto l'importazione dei namespaces è gestita
in modo diverso in C#

Questo tag è specifico di Visual Basic, perché ha lo scopo di inserire il Namespace o i Namespace che servono al codice contenuto nello snippet tra le clausole dei Reference del progetto. E' ignorato da C# e al momento in cui scrivo, non so se ve ne sia uno sostitutivo.

VB e C#
 
      <Declarations>
        ...
      </Declarations>

Anche questo è un tag contenitore, al suo interno vengono inserite le dichiarazioni di variabili, in questo caso ne vedremo una sola, che definisce il tipo di eccezione intercettata, ma nelle Declarations è possibile inserire un numero arbitrario di tag <Object> e <Literal> che permetteranno poi di inserire dei placeholder in ciascun punto dello snippet ove fosse necessario inserire un dato parametrico.

VB e C#
 
      <Object>
        <ID>ExceptionType</ID>
        <Type>Exception</Type>
        <ToolTip>Modificare con il tipo di eccezione specifica
            da catturare.</ToolTip>
        <Default>ApplicationException</Default>
      </Object>

<Object>, è un tag che permette la definizione di una variabile o meglio di una porzione di codice parametrico, contiene il tag <ID> al cui interno inseriremo la parola da utilizzare come placeholder nella forma $NomeVariabile$ all'interno del codice dello snippet. Il tag <Type> contiene il tipo di oggetto che andremo ad inserire con questo placeholder, in questo caso una Exception. Il tag <ToolTip> descrive cosa inserire e viene visualizzato da Intellisense. Il tag <Default> contiene il valore che deve assumere la variabile automaticamente quando viene generato il codice dello snippet, permettendoci di impostare il valore più utilizzato.

VB
 
      <Code Language="VB" Kind="method body">
        ...
      </Code>
C#
 
      <Code Language="csharp">
        ...
      </Code>

<Code> è il tag che contiene il codice dello snippet vero e proprio, anche se in realtà c'è un ulteriore tag di comodo; come vediamo, è in questo tag che è specificamente indicato il linguaggio in cui lo snippet è scritto.

VB
 
<![CDATA[
Try

Catch ex As $ExceptionType$
        throw new ApplicationException(" & & mClassName & "."_
            & System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
End Try]]>
C#
 
<![CDATA[
try
    {
        $end$$selected$
    }
    catch ($ExceptionType$ ex)
    {
        throw new ApplicationException(" " + mClassName + "."
            + System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
    }
]]>

L'ultimo tag, <![CDATA[...]]> questo tag XML è un tag di comodo che ha lo scopo di indicare al parser che tutto ciò che sta fra le due parentesi quadre più interne è da considerarsi testo indipendentemente da quello che contiene, in questo modo, se inserissimo dei tag XML di commento oppure facessimo uno snippet XML saremmo in grado di farlo e funzionerebbe correttamente.

All'interno del codice, possiamo notare il placeholder $ExceptionType$ da noi inserito per permetterci di modificare il tipo di eccezione intercettata; inoltre, nello script C# possiamo notare un'altro paio di placeholder che in VB non ci sono in quanto non gestiti: il placeholder $end$ dice all'editor di posizionare il cursore alla fine dell'inserimento dello snippet, il placeholder $selected$ indica all'editor dove deve essere copiato l'eventuale testo selezionato al momento dell'inserimento dello snippet, se viene richiesto un SurroundWith...

Creare uno snippet personalizzato
Ed ora che abbiamo descritto come è fatto uno snippet, ne scriviamo uno personalizzato. Per fare un esempio un po' corposo, ho deciso di predisporre due snippet, uno per la generazione di un evento con argomento standard ed uno per la generazione di un evento con un argomento personalizzato.

Evento semplice

VB
 
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>
        Generazione di un evento
      </Title>
      <Author>Sabrina C.</Author>
      <Shortcut>FyGenEventVb</Shortcut>
      <Description>Genera il codice necessario
        ad un evento personalizzato in una classe</Description>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>category</ID>
          <ToolTip>Categoria dell'evento</ToolTip>
          <Default>EventiPersonalizzati</Default>
        </Literal>
        <Literal>
          <ID>description</ID>
          <ToolTip>Descrizione dell'evento</ToolTip>
          <Default>Nuovo evento</Default>
        </Literal>
        <Literal>
          <ID>EventName</ID>
          <ToolTip>Nome dell'evento</ToolTip>
          <Default>MyEvent</Default>
        </Literal>
      </Declarations>
      <Code Language="VB">
        <![CDATA[
''' <summary>
''' Evento pubblico
''' </summary>
''' <remarks></remarks>
<Category("$category$"), Description("$description$")> _
Public Event $EventName$ As EventHandler
''' <summary>
''' Metodo da utilizzare per lanciare l'evento ove necessario
''' </summary>
''' <param name="e">Argomento dell'evento</param>
''' <remarks></remarks>
Protected Overridable Sub On$EventName$(ByVal e As EventArgs)
  RaiseEvent $EventName$(Me, e)
End Sub
'TODO: ove deve essere scatenato inserire il codice
On$EventName$(new EventArgs())
]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
C#
 
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>
        Generazione di un evento
      </Title>
      <Author>Rudy A.</Author>
      <Shortcut>FyGenEvent</Shortcut>
      <Description>Genera evento</Description>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>category</ID>
          <ToolTip>Categoria dell'evento</ToolTip>
          <Default>category</Default>
        </Literal>
        <Literal>
          <ID>description</ID>
          <ToolTip>Descrizione dell'evento</ToolTip>
          <Default>description</Default>
        </Literal>
        <Literal>
          <ID>eventname</ID>
          <ToolTip>Nome dell'evento</ToolTip>
          <Default>event_name</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
      <![CDATA[///<summary>
    /// Evento $description$
    ///</summary>
    ///<remarks>
    ///</remarks>
    [Category("$category$"), Description("$description$")]
    public event EventHandler $eventname$;
    //TODO: PER LANCIARE L'EVENTO
    //  On$eventname$(new EventArgs());
    $end$
    ///<summary>
    /// Invoca il delegate dell'evento $eventname$
    ///</summary>
    ///<remarks>
    ///</remarks>
    /// <param name="e">Argomenti evento</param>
    protected virtual void On$eventname$(EventArgs e)
    {
      if ($eventname$ != null)
      {
        // Invokes the delegates.
        $eventname$(this, e);
      }
    }]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Un Evento all'interno di una classe è una delle cose più comuni che sia necessario predisporre. Per aggiungere un evento ad una classe, sia questa una form, un componente o una classe di business logic, è necessario definire l'evento pubblico, definire un metodo Protected (Friend) e Overridable (Virtual), che scatena l'evento e poi, ove sia necessario scatenare l'evento, all'interno dei metodi o delle proprietà della classe, inserire la chiamata al metodo che scatena l'evento.

Come possiamo vedere, per questo evento abbiamo definito un nuovo tipo di tag nella zona <Declarations> ovvero il tag <Literal>, omologo di <Object>, ma predisposto per indicare un valore generico, anziché un tipo di oggetto preciso, (manca il tag <Type>); questo è solitamente il tipo di Declaration più utilizzato negli snippet. Per il resto, potete osservare come nel codice abbiamo inserito i placeholder dei parametri definiti e se provate ad inserirlo fra i vostri snippet e a chiamarlo vi chiederà di indicare i valori dei placeholder sparsi in tutto il suo codice.

Evento con argomento personalizzato

VB
 
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>
        Generazione di un evento con una classe argomento personalizzata
      </Title>
      <Author>Sabrina C.</Author>
      <Shortcut>FyGenEventArgVb</Shortcut>
      <Description>Genera il codice necessario ad un evento personalizzato in una classe con una
         classe eventhandler personalizzata</Description>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>category</ID>
          <ToolTip>Categoria dell'evento</ToolTip>
          <Default>EventiPersonalizzati</Default>
        </Literal>
        <Literal>
          <ID>description</ID>
          <ToolTip>Descrizione dell'evento</ToolTip>
          <Default>Nuovo evento</Default>
        </Literal>
        <Literal>
          <ID>EventName</ID>
          <ToolTip>Nome dell'evento</ToolTip>
          <Default>MyEvent</Default>
        </Literal>
        <Literal>
          <ID>ArgumentType</ID>
          <ToolTip>Sostituire con il tipo di argomento da passare</ToolTip>
          <Default>String</Default>
        </Literal>
        <Literal>
          <ID>ArgumentName</ID>
          <ToolTip>Nome dell'argomento base</ToolTip>
          <Default>MyArgument</Default>
        </Literal>
      </Declarations>
      <Code Language="VB">
        <![CDATA[
<Category("$category$"), Description("$description$")> _
Public Event $EventName$ As $EventName$EventHandler
'TODO: ove l'evento deve essere scatenato, inserire il seguente codice:
'On$EventName$( new $EventName$Eventhandler(valore as $ArgumentType$) )
''' <summary>
''' Gestore dell'evento personalizzato
''' </summary>
''' <param name="e">Argomento personalizzato dell'evento</param>
''' <remarks></remarks>
Protected Overridable Sub On$EventName$(ByVal e As $EventName$Eventhandler)
  RaiseEvent $EventName$(Me, e)
End Sub
''' <summary>
''' Classe argomento evento personalizzato per Evento $EventName$
''' </summary>
''' <remarks></remarks>
Public Class $EventName$EventHandler
  Inherits EventArgs
  Private m$ArgumentName$ As $ArgumentType$
  Public Sub New(ByVal p$ArgumentName$ As $ArgumentType$)
    Me.m$ArgumentName$ = p$ArgumentName$
  End Sub
  Public ReadOnly Property $ArgumentName$() As $ArgumentType$
    Get
      Return (Me.m$ArgumentName$)
    End Get
  End Property
End Class
''' <summary>
''' Delegate pubblico per la dichiarazione degli argomenti dell evento $EventName$
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Public Delegate Sub $EventName$EventHandler(ByVal sender As Object, ByVal e As $EventName$EventHandler)
]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>
C#
 
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets
    xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>
        Generazione di un evento con argomenti
      </Title>
      <Author>Sabrina C.</Author>
      <Shortcut>FyGenEventWithArgs</Shortcut>
      <Description>Genera evento</Description>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>category</ID>
          <ToolTip>Categoria dell'evento</ToolTip>
          <Default>category</Default>
        </Literal>
        <Literal>
          <ID>description</ID>
          <ToolTip>Descrizione dell'evento</ToolTip>
          <Default>description</Default>
        </Literal>
        <Literal>
          <ID>eventname</ID>
          <ToolTip>Nome dell'evento</ToolTip>
          <Default>event_name</Default>
        </Literal>
        <Literal>
          <ID>argumenttype</ID>
          <ToolTip>Tipo dell'argomento</ToolTip>
          <Default>string</Default>
        </Literal>
        <Literal>
          <ID>argumentname</ID>
          <ToolTip>Nome dell'argomento</ToolTip>
          <Default>ArgomentoDellEvento</Default>
        </Literal>
      </Declarations>
      <Code Language="csharp">
      <![CDATA[
    ///<summary>
    /// Evento $description$
    ///</summary>
    ///<remarks>
    ///</remarks>
    [Category("$category$"), Description("$description$")]
    public event $eventname$EventHandler $eventname$;
    //TODO: PER LANCIARE L'EVENTO
    //  On$eventname$( new $eventname$EventArgs(...));
    ///<summary>
    /// Invoca il delegate dell'evento $eventname$
    ///</summary>
    ///<remarks>
    ///</remarks>
    /// <param name="e">Argomenti evento</param>
    protected virtual void On$eventname$($eventname$EventArgs e)
    {
      if ($eventname$ != null)
      {
        // Invokes the delegates.
        $eventname$(this, e);
      }
    }
    //------------------------------------------------------------------------
    public class $eventname$EventArgs : System.EventArgs
    {
      ///<summary>
      /// $end$
      ///</summary>
      private readonly $argumenttype$ m$argumentname$;
      ///<summary>
      /// Costruttore
      ///</summary>
      public $eventname$EventArgs( $argumenttype$ p$argumentname$ )
      {
        this.m$argumentname$ = p$argumentname$;
      }
      ///<summary>
      /// $argumentname$
      ///</summary>
      public string $argumentname$
      {
        get
        {
          return( this.m$argumentname$ );
        }
      }
    }
    ///<summary>
    /// Delegate evento per utilizzare il $eventname$EventArgs
    ///</summary>
    public delegate void $eventname$EventHandler(object sender, $eventname$EventArgs e);]]>
      </Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Un evento, definito in una classe o in una Form o in uno User Control, molto spesso può avere la necessità di passare come parametro non il semplice argomento vuoto di default, ma uno o più argomenti che definiscano dei dati di contesto per indicare in quale condizione si è verificato l'evento (ad esempio un evento click su una toolbar deve restituirci il nome del bottone premuto).

Per generare un argomento parametrizzato, basta definire oltre all'evento una classe che eredita da EventArgs, ed un Delegate per la definizione dell'evento, oltre a quanto definito per l'evento semplice. Per fare tutto questo, abbiamo aggiunto due <Literal> per tipo e nome dell'argomento e naturalmente sviluppato il codice. E' ovvio che, una volta generato il codice da questo snippet, è opportuno spostare la classe generata fuori dalla classe che ospita l'evento in modo che sia resa correttamente visibile ovunque. Per prassi inoltre, nelle mie classi, gli eventi sono dichiarati tutti insieme a inizio classe, vicino alle variabili member e i metodi di generazione evento sono raggruppati in una regione, pertanto, dopo aver eseguito lo snippet e completata la generazione dell'evento, io sposto le porzioni di codice nel luogo corretto.

Conclusione
Con questo direi che ora potete divertirvi a standardizzare e parametrizzare tutto quello che vi garba.
I sorgenti dei miei esperimenti sono scaricabili dall'area download.

Feedback
Per commenti, richieste di chiarimenti, correzioni, su quanto esposto in questo articolo, potete scrivere sul blog dell'autrice.