[C#] Cum să redai un fișier audio WAV incorporat într-o resursă?

Metoda prezentată în acest articol este funcțională și în proiecte WinForms dar și în cele WPF.

// la începutul fișierului de cod-din-spate
// trebuie pus acest rând dacă nu există deja:
using System.Media;

Putem folosi metoda Load sau LoadAsync pentru a încărca fișierul wav înainte de redarea lui și intercepta evenimentul LoadCompleted pentru a acționa după încărcarea fișierului audio (din testele mele, acest eveniment este apelat doar când se oferă un Stream, nu un nume de fișier, și nu este apelat când încărcarea dă eroare), dar metodele Play, PlaySync și PlayLooping fac asta automat înainte de redare.

Dorim ca fișierul audio să fie preluat din dosarul executabilului

Adăugăm fișierul WAV la proiect prin meniul Project > Add Existing Item... apoi îl selectăm în Solution Explorer și în panoul Properties atribuim proprietății cu numele Copy to Output Directory valoarea Copy if newer.

// în metoda handler la clic pe un buton se pun următoarele rânduri
SoundPlayer p = new SoundPlayer("nume_fisier.wav");
p.PlayLooping(); // repetă la infinit sunetul; alternativ: p.Play() care nu redă repetat sau p.PlaySync() care blochează firul de execuție până la finalizarea redării

În cazul încărcării asincrone prin LoadAsync (care ajută la fișiere mari, dar am adăugat un fișier WAV de aprox 700MB la proiect și s-a blocat, la repornire nu era fișierul în proiect), poate fi folositor să declarăm variabila p (de tip SoundPlayer) la nivelul clasei (de ex. Form1).

Sau dorim fișierul audio incorporat în fișierul executabil

Adăugăm fișierul WAV la proiect prin meniul Project > Add Existing Item... apoi îl selectăm în Solution Explorer și în panoul Properties atribuim proprietății cu numele Build Action (ro. Acțiunea la construire) valoarea Embedded Resource (ro. Resursă incorporată). Proprietatea cu numele Copy to Output Directory nu mai are relevanță în acest exemplu.

Dacă setăm Copy to Output Directory la valoarea Do not copy, la următoarea compilare (construcție sau en. build), fișierul audio nu va mai fi în directorul bin/Debug (unde se află executabilul generat de obicei), dar datorită proprietății Build Action va fi conținut de executabil.

În exemplul acesta numele fișierului audio este chimes.wav, fișier preluat din link-ul de mai jos. În fereastra de proprietăți ale proiectului (clic-dreapta pe proiect în panoul Solution Explorer, apoi în meniul apărut se selectează Properties) se caută în secțiunea Application setarea „Default namespace” (spațiul de nume implicit al proiectului). În cazul exemplului, aceasta are valoarea „cs_soundplayer_test”.

System.Reflection.Assembly a =
    System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream s =

    a.GetManifestResourceStream("cs_soundplayer_test.chimes.wav");
SoundPlayer p = new SoundPlayer(s); // nu c-torul încarcă fluxul WAV în memorie ci apelul de mai jos:
p.PlayLooping();

Capturi de ecran








Secvențele de cod din acest articol au fost testate pe Windows 10 cu .NET Framework 4.6.1.

Inspirație [EN]:
Sunetele implicite din Windows 7 pot fi descărcate de aici: https://winsounds.com/windows-7-default-sounds/.

Documentație oficială [EN]: https://msdn.microsoft.com/en-us/library/system.media.soundplayer(v=vs.110).aspx.

[C# WinForms] ComboBox (via Dot Net Perls)

ComboBox este o cominație de TextBox cu un meniu drop-down. Lista sa drop-down preintă variante prealese. Utilizatorul poate tasta orice în ComboBox. Alternativ, el sau ea poate selecta ceva din listă.

Pentru a începe, creează o nouă aplicație Windows Forms și adaugă-i un ComboBox. Poți apoi face clic-dreapta pe ComboBox și adăuga handlere la evenimentele SelectedIndexChanged și TextChanged în panoul Properties (Proprietăți).

Program:
Când schimbi textul tastând în ComboBox sau făcând clic pe lista de elemente (Items), handlerele de evenimente sunt declanșate

Items:
Vezi secțiunea despre proprietatea Items înainte de a rula programul. Cu Items noi adăugăm șiruri de caractere (string-uri) la ComboBox.

Bazat pe: .NET (2018)


Program C# care demonstrează handlerele de evenimente ale ComboBox
 
using System;
using System.Windows.Forms;

namespace cs_winforms_combobox
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        int _selectedIndex;
        string _text;

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Apelat când un nou indice este selectat.
            _selectedIndex = comboBox1.SelectedIndex;
            Display();
        }

        private void comboBox1_TextChanged(object sender, EventArgs e)
        {
            // Apelat oricând textul se schimbă.
            _text = comboBox1.Text;
            Display();
        }

        void Display()
        {
            this.Text = string.Format("Text: {0}; SelectedIndex: {1}",
                _text,
                _selectedIndex);
        }
    }
}
 
Text. Proprietatea Text a ComboBox funcționează în mare parte ca proprietatea Text a unui TextBox. Dacă atribui proprietății Text, valoarea ccurentă din ComboBox se va schimba. Tu poți de asemenea citi proprietatea Text și să atribui variabile ei.

Tip:
Pentru a-l goli, poți să îi atribui un șir de caractere literal gol. Aceasta este o cale efectivă de a goli multe proprietăți.
Empty String [EN]
String Literal [RO]

Items. Proprietatea Items conține șiruri de caractere care sunt situate în partea drop-down a ComboBox-ului. Poți tasta Item-urile rând cu rând în Visual Studio prin fereastra de dialog Items, sau poți să le adaugi dinamic în timpul execuției.

Tip:
Pentru a adăuga Item-uri, apelez metoda Add: folosește sintaxa comboBox1.Add("Valoare"). Poți de asemenea goli („Clear”) ComboBox-ul cu Clear().

Utilizarea dialogului. Conceptual, ComboBox-ul este folosit pentru a reprezenta introducere de text cu un set de valori asociate predefinite care sunt ușor de selectat. Pentru acest motiv, este o alegere bună pentru dialoguri de preferințe.

Poți folosi controale ComboBox și avea valori presetate care sunt prezente în meniurile drop-down, dar să permiți utilizatorilor să selecteze orice valoare tastând-o direct înăuntru. Asta evită nevoia pentru mai mult de un control.

AutoComplete. Există 3 proprietăți pentru AutoComplete în ComboBox: AutoCompleteCustomSource, AutoCompleteMode, și AutoCompleteSource. Proprietatea AutoCompleteMode poate fi setată să sugereze, să adauge sau ambele.

Și:
Proprietățile Source îți permit să specifici setul de șiruri de caractere care sunt folosite ca sugestii.

Stiluri DropDown. Există 3 proprietăți de stil DropDown. Ele sunt DropDownHeight, DropDownStyle și DropDownWidth. Proprietățile DropDownHeight și DropDownWidth par a nu afecta aspectul vizual. Windows își folosește widget-urile implicite.

De asemenea:
Poți elimina drop-down-ul în întregime (cu Simple), sau să-l faci ca textul să nu fie editabil (cu DropDownList).

MaxDropDownItems. Această proprietate setează nr. maxim de elemente vizibile în drop-down. Nu are efect când IntegralHeight este setată la true. Dar dacă IntegralHeight este false, limitează nr. de elemente vizibile (o bară de glisare poate apărea).

Notă:
Mulțumiri lui Clarence Ravel pentru a arăta că MaxDropDownItems are un efect pentru un fals IntegralHeight.

Sumar:
Controlul ComboBox combină TextBox și un meniu listă drop-down. Reprezintă un widget hibrid folositor în Windows Forms. Este ideal pentru dialoguri unde unele sugestii pentru o intrare ar putea fi știute, dar orice valoare trebuie acceptată.

Recenzie:
ComboBox poate simplifica interfața ta prin unirea controalelor de interfață pentru utilizator.


Tradus de pe https://www.dotnetperls.com/combobox. Link la documentația oficială Microsoft a controlului ComboBox. Am adăugat și câteva capturi de ecran din Visual Studio:



Evenimentele ComboBox:





Proprietatea Items:





Programul în execuție:






[C#] Șir de caractere literal: linie-nouă și ghilimele (via Dot Net Perls)


Șir de caractere literal (en. String literal). Acesta constă în date constante sub forma unui șir de caractere. Datele șir de caractere sunt create în moduri diferite. Noi folosim literalele ca argumente la metode, sau oriunde un șir de caractere este cerut.

Cu un șir de caracter literal, caracterele sunt stocate direct în metadate. Mai puține ocoluri (care reduc performanța) sunt necesare.

Acest program conține șiruri de caractere literale. Șirurile de caractere literale la nivel de clasă sunt reprezentate ca referințe static sau const. Cele la nivel de metodă sunt tratate separat în metadate.
Static [EN]

Apoi:
Programele demonstrează literalele. Are caractere linie-nouă, tab-uri și ghilimele în literali.

Linii noi:
Acestea sunt specificate fie cu "\r\n" sau doar "\n". Și tab-urile sunt specificate cu "\t".

Ghilimelele:
Pentru ghilimele, noi folosim deseori un „backslash” (bară oblică înclinată spre stânga: "\"), dar pentru un literal textual (en. „verbatim”) (prefixat cu @), noi folosim două ghilimele pentru a ne referi la o ghilimea.

Program bazat pe .NET 2018:
Program C# care folosește șiruri de caractere literale

using System;

class Program
{
    static string _value1 = "String literal";
    const string _value2 = "String literal 2";
    const string _value3 = "String literal 3\r\nAnother line";
    const string _value4 = @"String literal 4
    Another line";
    const string _value5 = "String literal\ttab";
    const string _value6 = @"String literal\ttab";

    static void Main()
    {
        //
        // Motorul de execuție începe aici.
        //
        string test1 = "String literal \"1\"";
        const string test2 = "String literal 2";
        string test3 = @"String literal ""3""";
        const string test4 = @"String literal 4";
        //
        // Afișează șirurile de caractere literale.
        //
        Console.WriteLine(
            "{0}\n{1}\n{2}\n{3}\n{4}\n{5}\n{6}\n{7}\n{8}\n{9}",
            _value1, _value2, _value3, _value4, _value5, _value6,
            test1, test2, test3, test4);
    }
}
 
Ieșire

String literal
String literal 2
String literal 3
Another line
String literal 4
Another line
String literal  tab
String literal\ttab
String literal "1"
String literal 2
String literal "3"
String literal 4
 

Notă la programul de mai sus. Prima referință către șir literal este o variabilă statică, ceea ce înseamnă că va fi referită, în limbajul intermediar generat, în corpul metodelor unde este folosită.


Notă:
Folosind șirul de caractere static va cere motorului de execuție să rezolve simbolul _value1 în metadate.
Static String [EN]


Cu toate acestea:
Poți să reatribui variabilei _value1 oriunde în programul tău unde este accesibilă, cum ar fi în metoda Main.

Simbolul Arond. Patru dintre șirurile literale sunt prefixate cu simbolul @ înainte de semnele de citare. Acest simbol indică faptul că tu folosești sinaxa de șir literal textual.

Sfat:
Poți vedea că backslash-ul este tratat ca un caracter și nu o secvență de șir de evacuare când @ este folosit.

De asemenea:
Compilatorul C# îți permite să folosești linii noi reale în literalii textuali. Trebuie să codifici semnele de citare cu ghilimele duble.

Concat. Concatenarea variabilelor șiruri de caractere este făcută în timpul execuției („runtime”). Dar dacă o variabilă șir de caractere este constantă, compilatorul va genera limbaj intermediar cu concatenările eliminate.

Apoi:
Acest program pare a concatena 3 șiruri de caractere. Când este compilat IL arată că doar un singur șir este folosit.
string.Concat [EN]

Program C# care concatenează șiruri de caractere literale
using System;

class Program
{
    static void Main()
    {
        const string a = "Dot ";
        const string b = "Net ";
        const string c = "Perls";
        Console.WriteLine(a + b + c);
    }
}
 
Ieșire
Dot Net Perls

Limbaj intermediar: IL
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr      "Dot Net Perls"
  IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000a:  ret
} // end of method Program::Main



Metadate. Șirurile de caractere literale sunt stocate în format metadate. Acesta este definit în Specificația Limbajului Comun. El include o baza de date cu tabele cu antete și rânduri în toate fișierele exe.

Sfat:
Există câteva fluxuri predefinite în fișierele cu metadate , inclusiv #Strings și #US („user strings” - șiruri ale utilizatorului).

Fluxul #US este folosit pentru a stoca literali definiți de programator. Tabelele de metadate conțin ofseturi către acest flux. Fluxul însuși este o serie concatenată de caractere.

Notă:
Motorul de execuție reține ofseturile și tabelele în memorie și apoi citește un interval în fluxul #US.

Performanța. Înainte ca literalii șiruri de caractere să ajungă în metadate sau în instrucțiunile limbajului intermediar, compilatorul C# aplică o optimizare numită împăturirea constantelor („constant folding”).

Aici:
Constantele de tip șir de caractere literal sunt separate și partajate. Aplicarea împăturirii constantelor manual nu este cerută pentru eficiență.

Stocare:
Dacă folosești un anumit șir de caractere literal într-un program, el este stocat doar o singură dată în fluxul de șiruri de caractere ale utilizatorului („user strings stream”).

În concluzie:
Vedem tehnica compilatorului numită împăturirea constantelor aplicată la șiruri de caractere literale în programe C#.

Pe scurt. Literalii șiruri de caractere sunt specificați cu sintaxa șirurilor textuale („verbatim”). Noi folosm „backslash” ('\') pentru a „evacua” anumite secvențe. Șirurile de caractere literale sunt constante - ele nu pot fi schimbate.


Tradus de pe https://www.dotnetperls.com/string-literal. Am adăugat și 2 capturi de ecran Visual Studio.

[C#] Despre boxing și unboxing

Ce sunt boxing și unboxing?

string s = "test";

object obj = s; // boxing, nu trebuie folosit cast-ul

string str = (string)obj; // unboxing, trebuie folosit cast-ul

De ce se folosesc?

object este un tip-referință, string este un tip-valoare. În unele locuri e nevoie de folosirea unui tip-referință și nu a unui tip-valoare (când trebuie să apelezi o metodă care folosește metoda ToString a unui parametru de tip object sau o subclasă a object). Atunci e posibilă introducerea unei variabile de tip-valoare într-o variabilă de tip-referință. Și variabilele de tipuri-referință, și cele de tipuri-valoare se pot inițializa cu operatorul new când tipul respectiv are un constructor accesibil.

Ce este un tip-valoare?

int x = 5;
int y = 5;
Console.WriteLine(x == y); // Adevărat (True) pentru că valoarea lui y este o copie a valorii lui x, și se testează doar egalitatea valorilor, nu a adreselor de memorie ale variabilelor.

Tipurile-valoare se creează cu cuv. cheie struct. Documentație oficială în acest link [EN].

Ce este un tip-referință?

object a = 5;
object b = 5;
Console.WriteLine(a == b); // Fals (False) pentru că a și b sunt indicatori ai unei singure valori, 5, dar a face trimitere la a, și b face trimitere la b. Se compară adresele din memorie ale variabilelor.

Tipurile-referință se creează cu cuv. cheie class. Documentație oficială în acest link [EN].

Exemplu

using System;
using System.Windows.Forms;

namespace cs_boxing_unboxing
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string s = "test";
            object obj = s; // boxing, nu trebuie folosit cast-ul
            string str = (string)obj; // unboxing, trebuie folosit cast-ul
            Console.WriteLine(str);

            int x = 5;
            int y = 5;
            Console.WriteLine(x == y); // Adevărat (True) pentru că valoarea lui y este o copie a valorii lui x, și se testează doar egalitatea valorilor, nu a adreselor de memorie ale variabilelor.

            object a = 5;
            object b = 5;
            Console.WriteLine(a == b); // Fals (False) pentru că a și b sunt indicatori ai unei singure valori, 5, dar a face trimitere la a, și b face trimitere la b. Se compară adresele din memorie ale variabilelor.
        }
    }
}

Capturi de ecran


La rularea programului, se afișează:

 

[C#, WinForms] Exemple cu Form.DialogResult, Button.DialogResult și Form.ShowDialog

Intr-un Form afișat folosind metoda ShowDialog() putem crea un buton, programatic sau in designer, si sa-i dam o valoare proprietatii lui numita DialogResult care se returneaza din apelul ShowDialog:

Button btnOK = new Button()
{
    // ...
    DialogResult = DialogResult.OK,
    // ...
};

Dar putem in loc ca în handler-ul evenimentului Click al butonului respectiv sa setam proprietatea „DialogResult” a Form-ului pe care trebuie sa il inchida la, de exemplu, DialogResult.OK si atunci Form-ul nostru se inchide automat si metoda ShowDialog a Form-ului returneaza DialogResult.OK.

DialogResult este o enumerare care are elemente precum: Cancel, Yes, No, OK și None.

Capturi de ecran

Form1

Design

Cod din spate



Form2

Design

Cod din spate


Programul rulând

Fereastra principală a programului:


Apăsând pe butonul Form2.ShowDialog() se deschide acest formular:


Fereastra se poate închide fie prin butonul OK, când după închidere apare această fereastră:


Fie prin butonul Anulare, butonul X din bara de titlu sau combinația de taste Alt + F4, când după închidere apare această fereastră:



Dacă programul se închide forțat (din Managerul de activități - Task Manager, de exemplu) ferestrele din ultimele 2 capturi de ecran de mai sus nu se deschid.

Codul sursă

Form1.cs

using System;
using System.Windows.Forms;

namespace cs_form_dialogresult
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 frm2 = new Form2();
            DialogResult dr = frm2.ShowDialog(this);

            if (dr == DialogResult.OK)
            {
                MessageBox.Show(this,
                    "Butonul OK a fost apasat.");
            }
            else if (dr == DialogResult.Cancel)
            {
                MessageBox.Show(this,
                    "Butonul X sau Anuleaza a fost apasat.");
            }
            else
            {
                // Caz neasteptat, practic imposibil in
                // implementarea noastra.
                throw new NotImplementedException();
            }

            frm2.Dispose();
        }
    }
}

Form2.cs

using System;
using System.Windows.Forms;

namespace cs_form_dialogresult
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        private void btnAnuleaza_Click(object sender, EventArgs e)
        {
            // Functioneaza la fel ca la inchiderea
            // prin clic pe butonul X din bara de titlu:
            DialogResult = DialogResult.Cancel;
        }
    }
}

[C++, programare dinamică] Subsecvența de sumă maximă - problema (a) - suma și pozițiile de început și sfârșit

Enunțul subproblemei:
a) Modificați implementările date pentru a afișa și pozițiile de început și de sfârșit a unei subsecvențe de sumă maximă.
Problemă extrasă din cartea Algoritmica C++ de Vlad Sebastian Ionescu, Eugen Laslo.

Fișier intrare

10
-6 1 -3 4 5 -1 3 -8 -9 1

Fișier ieșire

11 4 7

Codul sursă

#include <fstream>
using namespace std;

const int inf = 1 << 30;
const int maxn = 2001;

int subsecv4(int A[], int N,
    int &inceput, int &sfarsit)
{
    int max = -inf;

    for (int st = 1; st < N; ++st)
    {
        for (int dr = st; dr <= N; ++dr)
        {

            int temp = 0;
            for (int i = st; i <= dr; ++i)
                temp += A[i];

            if (temp > max)
            {
                inceput = st;
                sfarsit = dr;
                max = temp;
            }
        }
    }

    return max;
}


int subsecv5(int A[], int N,
    int &inceput, int &sfarsit)
{
    int max = -inf;

    for (int st = 1; st < N; ++st)
    {
        int temp = 0;
        for (int dr = st; dr <= N; ++dr)
        {
            temp += A[dr];
            if (temp > max)
            {
                inceput = st;
                sfarsit = dr;
                max = temp;
            }
        }
    }

    return max;
}

int subsecv6(int A[], int N,
 int &inceput, int &sfarsit)
{
 int max = A[1];
 int inceput_temp = 1;
 int S[maxn];
 S[1] = A[1];
 inceput = sfarsit = 1;

 for (int i = 2; i <= N; ++i)
 {
  if (S[i - 1] + A[i] > A[i])
  {
   S[i] = S[i - 1] + A[i];
  }
  else
  {
   inceput_temp = i;
   S[i] = A[i];
  }

  if (S[i] > max)
  {
   inceput = inceput_temp;
   sfarsit = i;
   max = S[i];
  }
 }

 return max;
}

int main()
{
    int A[maxn], N;

    ifstream in("subsecv.in");
    in >> N;
    for (int i = 1; i <= N; ++i)
    {
        in >> A[i];
    }
    in.close();

    ofstream out("subsecv.out");
    int inceput, sfarsit;
    // Toate cele 3 functii de mai sus se
    // apeleaza identic.
    int max = subsecv4(A, N,
        inceput, sfarsit);
    out << max << " " << inceput <<
        " " << sfarsit << endl;
    out.close();

    return 0;
}

Adăugiri

Pe infoarena.ro am găsit o soluție de complexitate O(N) care, modificată puțin, dă și pozițiile de început și de sfărșit:


int subsecv_infoarena(int A[], int N,
 int &inceput, int &sfarsit)
{
 int sum[maxn], best[maxn];
 sum[0] = 0;
 for (int i = 1; i <= N; ++i)
 {
  sum[i] = A[i] + sum[i - 1];
 }

 int inceput_temp = 1;

 int min = sum[0];
 int bestSum = -inf;
 for (int i = 1; i <= N; ++i)
 {
  best[i] = sum[i] - min;
  if (min > sum[i])
  {
   min = sum[i];
   inceput_temp = i + 1;
  }
  if (bestSum < best[i])
  {
   bestSum = best[i];
   inceput = inceput_temp;
   sfarsit = i;
  }
 }

 return bestSum;
}

Alte date de intrare

12
99 -99 -6 1 -3 4 5 -1 3 -8 -9 1

și via infoarena.ro:

21
-1 2 3 -4 -2 2 1 -3 -2 -3 -4 9 -2 1 7 8 -19 -7 2 4 3

Continuare aici.

[C#] Cum se apelează o metodă generică prin Reflection

Pentru o scurtă introducere în lb. engleză în metodele generice în C# clic aici [1]. Pentru introducerea din documentația oficială C# în modulul Reflection, clic aici [2].

Echivalentul nefuncționalei formulări (extrasă dintr-un proiect, pentru caz mai simplu vedeți restul articolului):

    StergeRandCuCheiaPrimaraEgalaCu<cell.ValueType>(cell.Value);

este: 

    MethodInfo method = typeof(ClientiExistenti)
         .GetMethod("StergeRandCuCheiaPrimaraEgalaCu");
    MethodInfo generic = method.MakeGenericMethod(cell.ValueType);
    generic.Invoke(this, new object[] { cell.Value });

și nu trebuie omis rândul următor de la începutul fișierului .cs:
    using System.Reflection;

Capturi de ecran

1. Aici definim funcția privată nestatică generică MetodaGenerica:


2. Aici scriem cod care testează metoda din captura de mai sus.


3. Prezentăm abilitatea compilatorului C# de a deduce tipul generic.



4. Aici apare eroare fiindcă fără Reflection nu putem folosi un tip cunoscut doar la rulare ci un tip cunoscut și la compilare.



5. Aici folosim Reflection și dacă rulăm, nu va funcționa, deoarece metoda apelată prin Reflection trebuie să fie statică și publică:


6. Am rescris metoda și variabilele de care depinde pentru a fi publică și statică:




7. La rulare, apare următoarea fereastră (MessageBox):



Codul din penultima captură de mai sus

using System;
using System.Reflection;
using System.Windows.Forms;

namespace cs_reflection_metoda_generica
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public static object camp = null;
        public static string tipCamp = null;

        public static void MetodaGenerica<T>(T x)
        {
            camp = x;
            tipCamp = typeof(T).ToString();
        }

        private void Form1_Load(
            object sender, EventArgs e)
        {
            object x;

            // Aici se atribuie lui x
            // fie un `int`, fie un `string`.
            // Exemplu:
            x = 8;

            // Dacă vrem în loc de `int` un tip
            // știut doar în momentul rulării...
            //MetodaGenerica<x.GetType()>(x); // Eroare.

            // Metoda de mai jos cere ca metoda
            // apelată să fie statică și publică:
            MethodInfo method = typeof(Form1)
                .GetMethod("MetodaGenerica");
            MethodInfo generic = method.MakeGenericMethod(
                x.GetType());
            generic.Invoke(this, new object[] { x });

            MessageBox.Show($"{camp} " +
                $"este de tip {tipCamp}.");
        }
    }
}