Algoritmy a programování III

Týden 11

Pole, kolekce a LINQ

C# a .NET poskytují mnoho různých typů kolekcí. Pole mají syntaxi definovanou jazykem. Obecné typy kolekcí jsou uvedeny v System.Collections.Generic oboru názvů. Specializované kolekce zahrnují System.Span<T> přístup k nepřetržité paměti v rámci zásobníku a System.Memory<T> pro přístup k průběžné paměti ve spravované haldě. Všechny kolekce, včetně polí, Span<T>Memory<T> sdílejí jednotný princip iterace. Použijete System.Collections.Generic.IEnumerable<T> rozhraní. Tento princip sjednocení znamená, že kterýkoli z typů kolekcí lze použít s dotazy LINQ nebo jinými algoritmy. Metody, které používají IEnumerable<T> , a tyto algoritmy pracují s libovolnou kolekcí.

Pole

Pole je datová struktura, která obsahuje řadu proměnných, ke kterým se přistupuje prostřednictvím vypočítaných indexů. Proměnné obsažené v matici, označované také jako prvky pole, jsou všechny stejného typu. Tento typ se nazývá typ prvku pole.

Typy matice jsou odkazové typy a deklarace proměnné pole jednoduše vyhradí místo pro odkaz na instanci pole. Skutečné instance pole se vytvářejí dynamicky za běhu pomocí operátoru new . Operace new určuje délku nové instance pole, která se pak opraví po dobu životnosti instance. Indexy prvků pole jsou v rozsahu od 0 do Length - 1. Operátor new automaticky inicializuje prvky pole na výchozí hodnotu, což je například nula pro všechny číselné typy a null pro všechny odkazové typy.

Následující příklad vytvoří pole int prvků, inicializuje pole a vytiskne obsah pole.


int[] a = new int[10];
for (int i = 0; i < a.Length; i++)
{
    a[i] = i * i;
}
for (int i = 0; i < a.Length; i++)
{
    Console.WriteLine($"a[{i}] = {a[i]}");
}

Tento příklad vytvoří jednorozměrné pole a pracuje s tímto polem. Jazyk C# také podporuje vícerozměrná pole. Počet dimenzí typu pole, označovaný také jako pořadí typu matice, je jeden plus počet čárek mezi hranatými závorkami typu pole. Následující příklad přiděluje jednorozměrné, dvojrozměrné a trojrozměrné pole.


int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

Matice a1 obsahuje 10 prvků, a2 matice obsahuje 50 (10 × 5) prvků a a3 matice obsahuje 100 (10 × 5 × 2). Typ prvku pole může být libovolný typ, včetně typu pole. Matice s prvky typu pole se někdy nazývá jagged array , protože délky polí prvků nemusí být všechny stejné. Následující příklad přiděluje pole polí int:


int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

První řádek vytvoří pole se třemi prvky, každý typ int[] a každý s počáteční hodnotou null. Další řádky pak inicializují tři prvky s odkazy na jednotlivé instance pole s různými délkami.

Operátor new umožňuje zadat počáteční hodnoty prvků pole pomocí inicializátoru pole, což je seznam výrazů zapsaných mezi oddělovači { a }. Následující příklad přidělí a inicializuje se int[] třemi prvky.


int[] a = new int[] { 1, 2, 3 };

Délka pole je odvozena z počtu výrazů mezi { a }. Inicializace pole je možné dále zkrátit tak, aby typ pole nemusel být restován.


int[] a = { 1, 2, 3 };

Oba předchozí příklady jsou ekvivalentní následujícímu kódu:


int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;

Příkaz foreach lze použít k vytvoření výčtu prvků libovolné kolekce. Následující kód vytvoří výčet pole z předchozího příkladu:


foreach (int item in a)
{
    Console.WriteLine(item);
}

Příkaz foreach používá IEnumerable<T> rozhraní, aby mohl pracovat s libovolnou kolekcí.

Interpolace řetězců

Interpolace řetězců jazyka C# umožňuje formátovat řetězce definováním výrazů, jejichž výsledky jsou umístěny ve formátovacím řetězci. Například následující příklad vytiskne teplotu v daný den ze sady dat o počasí:


Console.WriteLine($"The low and high temperature on {weatherData.Date:MM-dd-yyyy}");
Console.WriteLine($"    was {weatherData.LowTemp} and {weatherData.HighTemp}.");
// Output (similar to):
// The low and high temperature on 08-11-2020
//     was 5 and 30.

Interpolovaný řetězec je deklarován pomocí tokenu $ . Interpolace řetězců vyhodnotí výrazy mezi { výrazy a }pak převede výsledek na a stringnahradí text mezi hranatými závorkami řetězcovým výsledkem výrazu. V : prvním výrazu {weatherData.Date:MM-dd-yyyy} určuje formátovací řetězec. V předchozím příkladu určuje, že datum se má vytisknout ve formátu MM-dd-rrrr.

Porovnávání vzorů

Jazyk C# poskytuje vzorové odpovídající výrazy pro dotazování stavu objektu a spuštění kódu na základě tohoto stavu. Můžete zkontrolovat typy a hodnoty vlastností a polí a určit, která akce se má provést. Můžete také zkontrolovat prvky seznamu nebo pole. Výraz switch je primární výraz pro porovnávání vzorů.

Delegáti a výrazy lambda

Typ delegáta představuje odkazy na metody s konkrétním seznamem parametrů a návratovým typem. Delegáti umožňují považovat metody za entity, které lze přiřadit proměnným a předat je jako parametry. Delegáti se podobají konceptu ukazatelů funkcí nalezených v některých dalších jazycích. Na rozdíl od ukazatelů funkce jsou delegáti objektově orientované a typově bezpečné.

Následující příklad deklaruje a používá typ delegáta s názvem Function.


 _factor = factor;    public double Multiply(double x) => x * _factor;
}class DelegateExample
{
    static double[] Apply(double[] a, Function f)
    {
        var result = new double[a.Length];
        for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
        return result;
    }    public static void Main()
    {
        double[] a = { 0.0, 0.5, 1.0 };
        double[] squares = Apply(a, (x) => x * x);
        double[] sines = Apply(a, Math.Sin);
        Multiplier m = new(2.0);
        double[] doubles = Apply(a, m.Multiply);
    }
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">delegate double Function(double x);class Multiplier
{
    double _factor;    public Multiplier(double factor) => _factor = factor;    public double Multiply(double x) => x * _factor;
}class DelegateExample
{
    static double[] Apply(double[] a, Function f)
    {
        var result = new double[a.Length];
        for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
        return result;
    }    public static void Main()
    {
        double[] a = { 0.0, 0.5, 1.0 };
        double[] squares = Apply(a, (x) => x * x);
        double[] sines = Apply(a, Math.Sin);
        Multiplier m = new(2.0);
        double[] doubles = Apply(a, m.Multiply);
    }
}

Instance Function typu delegáta může odkazovat na libovolnou metodu double , která přebírá argument a vrací double hodnotu. Metoda Apply použije danou Function na prvky , double[]vrací double[] s výsledky. Main V metodě Apply se používá k použití tří různých funkcí na .double[]

Delegát může odkazovat na výraz lambda k vytvoření anonymní funkce (například (x) => x * x v předchozím příkladu), statické metody (například Math.Sin v předchozím příkladu) nebo metody instance (například m.Multiply v předchozím příkladu). Delegát, který odkazuje na metodu instance také odkazuje na konkrétní objekt, a když je metoda instance vyvolána prostřednictvím delegáta, tento objekt se stane this ve vyvolání.

Delegáty je možné vytvořit také pomocí anonymních funkcí nebo výrazů lambda, které jsou "vložené metody", které se vytvářejí při deklaraci. Anonymní funkce mohou zobrazit místní proměnné okolních metod. Následující příklad nevytvoří třídu:


 x * 2.0);
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">double[] doubles = Apply(a, (double x) => x * 2.0);

Delegát neví nebo se nezajímá o třídu metody, na které odkazuje. Odkazovaná metoda musí mít stejné parametry a návratový typ jako delegát.

async / await

Jazyk C# podporuje asynchronní programy se dvěma klíčovými slovy: async a await. Modifikátor přidáte async do deklarace metody, která deklaruje metodu, je asynchronní. Operátor await říká kompilátoru, aby asynchronně čekal na dokončení výsledku. Ovládací prvek se vrátí volajícímu a metoda vrátí strukturu, která spravuje stav asynchronní práce. Struktura je obvykle typu System.Threading.Tasks.Task<TResult>, ale může být libovolný typ, který podporuje model awaiter. Tyto funkce umožňují psát kód, který čte jako synchronní protějšek, ale provádí se asynchronně. Například následující kód stáhne domovskou stránku dokumentace Microsoftu:


 RetrieveDocsHomePage()
{
    var client = new HttpClient();
    byte[] content = await client.GetByteArrayAsync("https://learn.microsoft.com/");    Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished downloading.");
    return content.Length;
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public async Task<int> RetrieveDocsHomePage()
{
    var client = new HttpClient();
    byte[] content = await client.GetByteArrayAsync("https://learn.microsoft.com/");    Console.WriteLine($"{nameof(RetrieveDocsHomePage)}: Finished downloading.");
    return content.Length;
}

Tato malá ukázka ukazuje hlavní funkce pro asynchronní programování:

  • Deklarace metody obsahuje async modifikátor.
  • Tělo metody awaitje návrat metody GetByteArrayAsync .
  • Typ zadaný v return příkazu odpovídá argumentu typu v Task<T> deklaraci pro metodu. (Metoda, která vrátí Task příkazy use return bez argumentu).

Atributy

Typy, členy a další entity v programu jazyka C# podporují modifikátory, které řídí určité aspekty jejich chování. Například přístupnost metody se řídí pomocí publicmodifikátorů , protectedinternalprivate modifikátorů. Jazyk C# tuto schopnost zobecňuje tak, aby uživatelem definované typy deklarativních informací bylo možné připojit k entitám programu a načíst je za běhu. Programy určují tyto deklarativní informace definováním a použitím atributů.

Následující příklad deklaruje HelpAttribute atribut, který lze umístit do entit programu a poskytnout odkazy na jejich přidruženou dokumentaci.


 _url = url;    public string Url => _url;    public string Topic
    {
        get => _topic;
        set => _topic = value;
    }
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public class HelpAttribute : Attribute
{
    string _url;
    string _topic;    public HelpAttribute(string url) => _url = url;    public string Url => _url;    public string Topic
    {
        get => _topic;
        set => _topic = value;
    }
}

Všechny třídy atributů jsou odvozeny od Attribute základní třídy poskytované knihovnou .NET. Atributy lze použít zadáním jejich názvu spolu s libovolnými argumenty uvnitř hranatých závorek těsně před přidruženou deklarací. Pokud název atributu končí Attribute, může být tato část názvu vynechána při odkazování na atribut. Můžete ho HelpAttribute například použít následujícím způsobem.


[Help("https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/features")]
public class Widget
{
    [Help("https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/features",
    Topic = "Display")]
    public void Display(string text) { }
}

Tento příklad připojí třídu HelpAttributeWidget . Přidá další HelpAttribute metodu Display ve třídě. Veřejné konstruktory třídy atributu řídí informace, které musí být poskytnuty, když je atribut připojen k entitě programu. Další informace lze poskytnout odkazováním na veřejné vlastnosti pro čtení a zápis třídy atributu (například odkaz na Topic vlastnost dříve).

Metadata definovaná atributy je možné číst a zpracovávat za běhu pomocí reflexe. Pokud je požadován konkrétní atribut pomocí této techniky, konstruktor třídy atributu je vyvolán s informacemi zadanými ve zdroji programu. Vrátí se výsledná instance atributu. Pokud byly prostřednictvím vlastností poskytnuty další informace, jsou tyto vlastnosti nastaveny na dané hodnoty před vrácením instance atributu.

Následující ukázka kódu ukazuje, jak získat HelpAttribute instance přidružené ke Widget třídě a jeho Display metodě.


 0)
{
    HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
    Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);if (displayMethodAttributes.Length > 0)
{
    HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
    Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">Type widgetType = typeof(Widget);object[] widgetClassAttributes = widgetType.GetCustomAttributes(typeof(HelpAttribute), false);if (widgetClassAttributes.Length > 0)
{
    HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
    Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}System.Reflection.MethodInfo displayMethod = widgetType.GetMethod(nameof(Widget.Display));object[] displayMethodAttributes = displayMethod.GetCustomAttributes(typeof(HelpAttribute), false);if (displayMethodAttributes.Length > 0)
{
    HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
    Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}