PROGRAMOWANIE – PRZYKŁADY
IDE C++ WinBGI C# Wpadki

Przykład C#

Kolorowanie kalendarza miesięcznego Struktury a klasy Klasa wyznaczania dni świątecznych Przekazywanie parametrów przez wartość i referencję Program w Visual C# Poprzedni przykład Następny przykład Program w C++ Kontakt

Kolorowanie kalendarza miesięcznego

Program wyświetlania kalendarza miesięcznego stałby się bardziej atra­kcyjny, gdyby niedziele i święta oraz datę bieżącą wyróżniał innym kolorem, np. czerwo­nym i fiole­towym. Zestaw świąt kościel­nych i państwowych w Polsce zmieniał się co jakiś czas, toteż dla uproszczenia dalszych rozważań uwzglę­dnimy stan obecnie obowią­zujący:

1 stycznia          – Nowy Rok
6 stycznia          – Trzech Króli (od 2011 r.)
? (marzec/kwiecień) – Wielkanoc (1 dzień), święto ruchome
? (marzec/kwiecień) – Wielkanoc (2 dzień), święto ruchome
1 maja              – Święto Pracy
3 maja              – Święto Konstytucji 3 Maja
? (maj/czerwiec)    – Boże Ciało, święto ruchome
15 sierpnia         – Wniebowzięcie NMP
1 listopada         – Wszystkich Świętych
11 listopada        – Święto Niepodległości
25 grudnia          – Boże Narodzenie (1 dzień)
26 grudnia          – Boże Narodzenie (2 dzień)

Wyznaczenie dat niedziel i świąt stałych nie nastrę­cza kłopotu, problem sprawiają daty świąt ruchomych – Wielka­nocy (właściwie Ponie­działku Wielka­nocnego) i Bożego Ciała. Wielka­noc przypada w pierwszą niedzielę po pierwszej pełni Księżyca po równo­nocy wiosennej, Ponie­działek Wielka­nocny jest pierwszym dniem po Wielka­nocy, zaś Boże Ciało sześćdzie­siątym. Problem wyzna­czenia dat świąt ruchomych spro­wadza się zatem do obli­czenia daty Wielka­nocy. Zagadnie­niem tym zajmował się m.in. znako­mity niemiecki matematyk, fizyk, astronom i geodeta Carl Friedrich Gauss (1777–1855). Jego nieco zmodyfi­kowany algorytm zapisany w C#, w którym r oznacza numer roku i wszystkie zmienne są typu int, przebiega następująco:

G = r % 19;
C = r / 100;
H = (C - C/4 - (8*C + 13) / 25 + 19*G + 15) % 30;
I = H - (H/28) * (1 - (H/28) * 29 / (H+1) * (21-G) / 11);
J = (r + r/4 + I + 2 - C + C/4) % 7;
L = I - J;
M = 3 + (L+40) / 44;       // Miesiąc Wielkanocy
D = L + 28 - 31 * (M/4);   // Dzień Wielkanocy

Struktury a klasy

Struktury są typami danych złożonymi z danych niekonie­cznie tego samego typu, zwanych polami. Podobnie jak klasy, struktury mogą również posiadać metodywłaści­wości. Poniżej podany jest przykład prostego programu, w którym zdefi­niowano typ struktu­ralny Date złożony z trzech pól przezna­czonych do zapamię­tania daty, a następnie w funkcji Main zadekla­rowano zmienną dt tego typu i przypi­sano jej polom wartości określa­jące datę 3 maja 2018 roku, po czym wartości tych pól wyprowa­dzono do okna konsoli.

using System;

class Program
{
    struct Date
    {
        public int   da_year;   // rok
        public sbyte da_day;    // dzień
        public sbyte da_mon;    // miesiąc
    }

    static void Main()
    {
        Date dt;
        dt.da_year = 2018;
        dt.da_day = 5;
        dt.da_mon = 3;
        Console.WriteLine("{0:0#}.{1:0#}.{2}", dt.da_day, dt.da_mon, dt.da_year);
    }
}

Wynikiem wykonania programu jest tekst

03.05.2018

bowiem specyfi­katory formato­wania dwóch pól zawierają symbole 0 i #. Pierwszy jest symbolem zastępczym dla zera (niezna­czące zero jest drukowane), drugi dla cyfry (niezna­czące zero jest pomijane). Uzupełnijmy jeszcze, że typ Date jest równoważny typowi date w Borland C++.

W trakcie pobieżnego omawiania typu struktu­ralnego DateTime zdefiniowanego w prze­strzeni System nadmie­niono, że można go traktować jak uproszczoną klasę. Istotnie, struktury są pozbawione niektórych cech klas, ale są również bardzo podobne do klas i korzy­stanie z nich jest niemal iden­tyczne. Istnieje jednak pomiędzy nimi zasa­dnicza różnica:

Typami warto­ściowymi w C# są oprócz struktur podstawowe typy danych, takie jak char, byte, bool, int, long, float, double i typy wylicze­niowe enum, natomiast typami referen­cyjnymi są łańcuchy string, tablice i klasy. Podczas definio­wania zmiennej typu wartościo­wego przydzie­lany jest jej obszar pamięci, w którym ma być przecho­wywana wartość tej zmiennej. Przykładem takiej zmiennej jest struktura dt typu Date w powyższym programie, dlatego po definicji zmiennej od razu można było przypisać wartości jej polom. W przy­padku typu referen­cyjnego zmienna służy jedynie do pamiętania refe­rencji do obiektu, która jest wartością informu­jącą o położeniu obiektu w pamięci. Rezer­wacja obszaru pamięci dla obiektu i wygenero­wanie referencji do niego następuje w momencie tworzenia tego obiektu za pomocą opera­tora new odwołu­jącego się do konstru­ktora danej klasy. Gdyby w defi­nicji typu Date w przyto­czonym programie zastąpić słowo kluczowe struct słowem class, w instru­kcji przypisu­jącej wartość 2018 polu dt.da_year wystą­piłby błąd pole­gający na tym, że zmienna dt nie ma przypi­sanej wartości. Błąd możnaby naprawić, tworząc obiekt klasy Date i przypi­sując referencję do niego zmiennej dt:

dt = new Date();

Jak wiadomo, struktury mogą posiadać metody. Co więcej, mogą nawet posiadać konstru­ktor. Definio­wany konstru­ktor struktury powinien mieć przynaj­mniej jeden argument i przypi­sywać wartości wszystkim jej polom (niespeł­nienie tych warunków kończy się błędem kompi­lacji). Oto zmodyfi­kowana wersja powyż­szego programu, w której użyto konstru­ktora struktury Date:

using System;

class Program
{
    struct Date
    {
        public int   da_year;   // rok
        public sbyte da_day;    // dzień
        public sbyte da_mon;    // miesiąc
        public Date(int d, int m, int r)
        {
            da_year = r;
            da_mon = (sbyte)m;
            da_day = (sbyte)d;
        }
    }

    static void Main()
    {
        Date dt = new Date(3, 5, 2018);
        Console.WriteLine("{0:0#}.{1:0#}.{2}", dt.da_day, dt.da_mon, dt.da_year);
    }
}

Klasa wyznaczania dni świątecznych

Przez analogię do programu kolorowania kalendarza w C++ wykaz świąt stałych i ruchomych (bez niedzieli wielka­nocnej) może być reprezen­towany przez tablicę, której elementami są struktury złożone z dwóch pól całko­witych określa­jących miesiąc i dzień, w którym przypada święto. Przy tak prostej strukturze inicjali­zacja 11 ele­mentów tablicy wymaga­łaby użycia aż 22 przypisań:

struct Dz_sw
{
    public int m, d;
}
...
Dz_sw[] Rd = new Dz_sw[11];
Rd[0].m = 1;               // Nowy rok (miesiąc)
Rd[0].d = 1;               // Nowy rok (dzień)
...                        // ...
Rd[10].m = 12;             // Boże Narodzenie (miesiąc)
Rd[10].d = 26;             // Boże Narodzenie (dzień)

Oczywiście wartości dwóch elementów tablicy Rd, odpowia­dające Ponie­działkowi Wielka­nocnemu i świętu Bożego Ciała, można wyznaczyć dopiero wtedy, gdy znany jest numer roku. Zdefinio­wanie konstru­ktora struktury Dz_sw inicjalizu­jącego jej pola prowadzi do znacznie lepszego rozwiązania:

struct Dz_sw
{
    public int m, d;
    public Dz_sw(int m, int d)
    {
        this.m = m;
        this.d = d;
    }
}
...
Dz_sw[] Rd = new Dz_sw[]
{
    new Dz_sw(1, 1),        // Nowy rok
    new Dz_sw(1, 6),        // Trzech Króli (od 2011 r.)
    new Dz_sw(),            // Poniedziałek Wielkanocny
    new Dz_sw(5, 1),        // Święto Pracy
    new Dz_sw(5, 3),        // Święto Konstytucji 3 Maja
    new Dz_sw(),            // Boże Ciało
    new Dz_sw(8, 15),       // Wniebowzięcie NMP
    new Dz_sw(11, 1),       // Wszystkich Świętych
    new Dz_sw(11, 11),      // Święto Niepodległości
    new Dz_sw(12, 25),      // Boże Narodzenie (1 dzień)
    new Dz_sw(12, 26)       // Boże Narodzenie (2 dzień)
};

W przypadku wspomnianych dwóch elementów tablicy Rd, o indeksach 2 (Poniedziałek Wielkanocny) i 5 (Boże Ciało), użyto konstru­ktora domyślnego, który inicja­lizuje obydwa pola zerem. Po ich uaktual­nieniu nietrudno jest sprawdzić, przeszu­kując tablicę sekwen­cyjnie, czy data określona przez numer miesiąca i numer dnia przypada w danym roku w dzień święteczny, czy nie:

bool Swieto(int m, int d)
{
    for (int k = 0; k < Rd.Length; k++)
        if (m == Rd[k].m && d == Rd[k].d)
            return true;
    return false;
}

Działanie metody Swieto polega na kolejnym przeglądaniu dat zapisanych w tablicy Rd i spraw­dzaniu, czy któraś z nich jest zgodna z datą określoną w argu­mentach. Jeśli poszuki­wana data występuje w tablicy, przeszuki­wanie kończy się powodze­niem – pętla zostaje przerwana i metoda zwraca wartość true (dzień świąte­czny). Jeśli poszuki­wana data nie występuje w tablicy, pętla kończy się normalnie i metoda zwraca wartość false (dzień zwykły). Wygodniej­szym sposo­bem przebie­gania po kolej­nych elemen­tach tablicy jest użycie pętli foreach:

bool Swieto(int m, int d)
{
    foreach (Dz_sw x in Rd)
        if (m == x.m && d == x.d)
            return true;
    return false;
}

Elementy tablicy Rd są w kolejnych krokach foreach utożsa­miane ze zmienną itera­cyjną x typu Dz_sw, dla której jest wykony­wana instru­kcja if. Należy pamiętać, że w przeci­wieństwie do for wartości zmiennej itera­cyjnej pętli foreach są dostępne tylko do odczytu (nie można ich modyfikować).

Podobnie jak w przy­padku klasy obliczeń kalenda­rzowych Kalend, metody realizujące obliczenia dotyczące wyznaczania świąt stałych i rucho­mych zgrupu­jemy w klasie Swieta przestrzeni Kalendarz.Greg. Klasa ma stanowić odpowie­dnik modułu obliczeniowego swieta w języku C++, toteż powinna obejmować metody:

Ponadto powinna zawierać definicję struktury Dz_sw i dekla­rację tablicy (pola) Rd o ele­mentach typu Dz_sw. Warto przy okazji odno­tować, że w języku C# pojęcia definicjadekla­racja są używane zamiennie, nie ma pomiędzy nimi ścisłego rozgrani­czenia jak w C++, gdyż nie ma takiej potrzeby.

Oto kompletny kod źródłowy klasy Swieta wraz z komen­tarzami XML uzupełnia­jącymi informacje wyświe­tlane w podpo­wiedziach systemu IntelliSence:

/// <summary>
/// Kalendarz, święta stałe i ruchome
/// </summary>
namespace Kalendarz.Greg
{
    /// <summary>
    /// Święta stałe i ruchome
    /// </summary>
    public class Swieta
    {
        /// <summary>
        /// Tworzy listę świąt stałych i ruchomych
        /// </summary>
        /// <param name="r">numer roku</param>
        public Swieta(int r)
        {
            Rd = new Dz_sw[]
            {
                new Dz_sw(1, 1),        // Nowy rok
                new Dz_sw(1, 6),        // Trzech Króli (od 2011 r.)
                new Dz_sw(),            // Poniedziałek Wielkanocny
                new Dz_sw(5, 1),        // Święto Pracy
                new Dz_sw(5, 3),        // Święto Konstytucji 3 Maja
                new Dz_sw(),            // Boże Ciało
                new Dz_sw(8, 15),       // Wniebowzięcie NMP
                new Dz_sw(11, 1),       // Wszystkich Świętych
                new Dz_sw(11, 11),      // Święto Niepodległości
                new Dz_sw(12, 25),      // Boże Narodzenie (1 dzień)
                new Dz_sw(12, 26)       // Boże Narodzenie (2 dzień)
            };
            Uzup(r);
        }

        /// <summary>
        /// Uzupełnia listę świąt stałych o święta ruchome
        /// </summary>
        /// <param name="r">numer roku</param>
        public void Uzup(int r)
        {
            int G = r % 19,
                C = r / 100,
                H = (C - C / 4 - (8 * C + 13) / 25 + 19 * G + 15) % 30,
                I = H - (H / 28) * (1 - (H / 28) * 29 / (H + 1) * (21 - G) / 11),
                J = (r + r / 4 + I + 2 - C + C / 4) % 7,
                L = I - J,
                M = 3 + (L + 40) / 44,      // Miesiąc Wielkanocy
                D = L + 28 - 31 * (M / 4);  // Dzień Wielkanocy

            if (D < 31)                     // Jeżeli Wielkanoc nie przypada
            {                               // 31 dnia miesiąca (31 marca),
                Rd[2].m = M;                // Poniedziałek Wielkanocny
                Rd[2].d = D + 1;            // przypada o dzień później.
            }
            else                            // W przeciwnym razie
            {                               // Poniedziałek Wielkanocny
                Rd[2].m = 4;                // przypada w następnym miesiącu,
                Rd[2].d = 1;                // tj. 1 kwietnia.
            }
            if (D > 1)                      // Jeżeli Wielkanoc nie przypada
            {                               // 1 dnia miesiąca (1 kwietnia),
                Rd[5].m = M + 2;            // Boże Ciało przypada po dwóch
                Rd[5].d = D - 1;            // miesiącach o dzień wcześniej.
            }
            else                            // W przeciwnym razie
            {                               // Boże Ciało przypada
                Rd[5].m = 5;                // w następnym miesiącu,
                Rd[5].d = 31;               // tj. 31 maja.
            }
        }

        /// <summary>
        /// Sprawdza, czy określony dzień miesiąca jest świętem
        /// </summary>
        /// <param name="m">numer miesiąca</param>
        /// <param name="d">numer roku</param>
        /// <returns>false - nie, true - tak</returns>
        public bool Swieto(int m, int d)
        {
            foreach (Dz_sw x in Rd)
                if (m == x.m && d == x.d)
                    return true;
            return false;
        }

        /// <summary>
        /// Dzień świąteczny
        /// </summary>
        private struct Dz_sw
        {
            public int m, d;
            public Dz_sw(int m, int d)
            {
                this.m = m;
                this.d = d;
            }
        }

        /// <summary>
        /// Tablica (lista) dni świątecznych
        /// </summary>
        private Dz_sw[] Rd;
    }
}

Jak widać, konstruktor klasy Swieta tworzy tablicę Rd i inicja­lizuje jej elementy datami świąt stałych, a nastę­pnie uzu­pełnia jej dwa elementy o daty świąt rucho­mych, wywołując metodę Uzup dla określo­nego numeru roku. Z kolei metoda Uzup wyznacza datę Wielka­nocy według algorytmu Gaussa, a potem w pierwszej instrukcji warun­kowej if—else datę Ponie­działku Wielkano­cnego, zaś w drugiej datę Bożego Ciała. Numer dnia pierwszej daty jest o 1 większy od numeru dnia daty Wielka­nocy, jeśli ten drugi jest mniejszy od 31, tj. gdy Wielkanoc nie przypada 31 marca. W prze­ciwnym razie Ponie­działek Wielka­nocny przypada 1 kwie­tnia. Natomiast numer dnia daty Bożego Ciała, odległej od daty Wielka­nocy o 60 dni, jest zazwyczaj o 1 mniejszy od numeru dnia daty Wielka­nocy, a numer miesiąca większy o 2. Nie jest tak tylko wtedy, gdy Wielkanoc przypada 1 kwie­tnia. Wówczas Boże Ciało przypada 31 maja.

Przekazywanie parametrów przez wartość i referencję

Pojęcia parametr i argument są często używane zamiennie. Wypada zatem wyjaśnić, że słowo parametr powinno być stosowane dla zmiennej zadekla­rowanej w nagłówku metody, zaś argument dla faktycznej danej przeka­zywanej do para­metru w wywołaniu metody. Czasami dla takiego rozróż­nienia używa się bardziej sugesty­wnych zwrotów parametr formalnyparametr aktualny lub też argument formalnyargument aktualny. Podczas wywo­łania metody parametry aktualne są podsta­wiane w miejsce formalnych, po czym następuje przejście jej wykonania. Typy parame­trów aktu­alnych muszą być zgodne z typami przyporząd­kowanymi odpowie­dnim parame­trom formalnym. W języku C# wyróżnia się dwa rodzaje przeka­zywania (podsta­wiania) para­metrów (argu­mentów):

Przekazywanie przez wartość polega na przesłaniu wartości parametru aktual­nego do odpowie­dniego parametru formal­nego, który jest trakto­wany jak zmienna lokalna zadekla­rowana na początku metody. Zmiana wartości takiej zmiennej wewnątrz metody nie ma już żadnego związku z para­metrem aktualnym. Ponieważ podsta­wienie wartości występuje najczęściej, wybierane jest domyślnie, gdy rodzaj przeka­zywania parame­trów nie został podany.

W poniższym przykładzie w momencie wywołania metody Proc wartość 2 parametru aktual­nego p zostaje przypisana parame­trowi formal­nemu x i na tym kończy się związek pomiędzy tymi parame­trami. Zwiększenie wartości x o 10 nie powo­duje więc zmiany wartości p. Wynika stąd, że wyświe­tlanym w oknie konsoli wynikiem jest 2.

using System;

class Program
{
    static void Proc(int x)
    {
        x += 10;
    }

    static void Main()
    {
        int p = 2;
        Proc(p);
        Console.WriteLine("Wynik: {0}", p);
    }
}

Przekazywanie przez referencję polega na przesłaniu do metody refe­rencji do zmiennej (adresu obszaru pamięci przydzie­lonego zmiennej) pełniącej rolę para­metru aktualnego. Oznacza to, że parametr formalny zajmuje tę samą pamięć co parametr aktualny i zmiana jego wartości jest w istocie zmianą wartości orygi­nalnej zmiennej. Przekazy­wanie przez refe­rencję wymaga użycia słowa kluczowego ref lub out na liście parametrów metody i jej wywo­łania. Słowo out stosuje się w przypadku tzw. parametrów wyjściowych, których wartość jest w chwili rozpo­częcia wykony­wania metody nieokre­ślona lub nieistotna, a po jej wyzna­czeniu stanowi wynik działania metody.

W sformułowanym ponownie przykładzie zarówno przed parametrem formal­nym x, jak i przed parametrem aktualnym p występuje słowo kluczowe ref. Zatem przekazana do metody Proc zmienna p o wartości 2 jest utożsa­miana wewnątrz metody z para­metrem x, przez co zwiększenie wartości x o 10 jest w rzeczywi­stości zwiększe­niem wartości p o 10. Wyświe­tlanym wynikiem jest więc 12.

using System;

class Program
{
    static void Proc(ref int x)
    {
        x += 10;
    }

    static void Main()
    {
        int p = 2;
        Proc(ref p);
        Console.WriteLine("Wynik: {0}", p);
    }
}

Obydwa rodzaje podstawień parametrów mogą być stoso­wane zarówno w odnie­sieniu do typów wartościo­wych, jak i referen­cyjnych. Ich efekty warto prześle­dzić na przykła­dzie parametrów typu Punkt definio­wanego jako struktura, a także jako klasa i reprezen­tującego punkt o dwóch współ­rzędnych całko­witych. W rozpatry­wanych poniżej wersjach prostego programu metoda Proc zmienia współ­rzędne punktu przeka­zanego jej przez parametr p o wartości 40 i -60 dwóch kolejnych parame­trów dxdy. Intere­suje nas, jak wywołanie metody Proc wpływa na zmienną pkt reprezen­tującego punkt o współrzę­dnych 10, 20 i pełniącą rolę parametru aktualnego.

W zaprezentowanej poniżej pierwszej wersji programu przeka­zywane przez wartość parametry są typu warto­ściowego (struct). W zmiennej pkt przecho­wywana jest bezpo­średnio jej wartość (współ­rzędne punktu), która w momencie wywołania metody Proc zostaje skopiowana do zmiennej p zajmującej odrębny obszar pamięci. Zmiana wartości pól zmiennej p nie wpływa więc na wartość zmiennej pkt, toteż wynikiem programu są liczby 10, 20.

using System;

class Program
{
    struct Punkt
    {
        public int x, y;
        public Punkt(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    static void Proc(Punkt p, int dx, int dy)
    {
        p.x += dx;
        p.y += dy;
    }

    static void Main()
    {
        Punkt pkt = new Punkt(10, 20);
        Proc(pkt, 40, -60);
        Console.WriteLine("Punkt: {0}, {1}", pkt.x, pkt.y);
    }
}

Kod źródłowy drugiej wersji programu różni się od pierwszej jedynie użyciem w defi­nicji typu Punkt słowa kluczowego class zamiast struct. Zmiana ta oznacza, że zmienna pkt jest typu referen­cyjnego i zawiera jedynie refe­rencję do obiektu (adres obszaru pamięci obiektu) reprezen­tującego punkt, a ponieważ nadal jest przeka­zywana do metody Proc przez wartość, parametr p zawiera kopię tej refe­rencji. Obie refe­rencje odwołują się do tego samego obiektu, zmiana jego pól wewnątrz metody Proc wpływa zatem na wynik pokazywany poza nią, którym są liczby 50, -40.

W trzeciej wersji programu wracamy do typu struktu­ralnego Point i dopisujemy przed pierwszym parame­trem metody Proc i jej wywołania słowo kluczowe ref (pełny kod źródłowy poniżej). Tym razem zmienna pkt typu warto­ściowego reprezen­tujaca punkt zostaje przeka­zana do metody przez refe­rencję, zatem parametr p zajmuje tę samą pamięć co pkt i zmiana wartości p jest jedno­cześnie zmianą wartości pkt. Wynikiem są więc liczby 50, -40.

using System;

class Program
{
    struct Punkt
    {
        public int x, y;
        public Punkt(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    static void Proc(ref Punkt p, int dx, int dy)
    {
        p.x += dx;
        p.y += dy;
    }

    static void Main()
    {
        Punkt pkt = new Punkt(10, 20);
        Proc(ref pkt, 40, -60);
        Console.WriteLine("Punkt: {0}, {1}", pkt.x, pkt.y);
    }
}

Ostatnią, czwartą już wersję programu tworzymy, zastępując w definicji typu Punkt powyższej wersji programu słowo kluczowe struct słowem class. W rezul­tacie zmienna pkt typu referen­cyjnego odwołu­jąca się do obiektu klasy Punkt zostaje przekazana przez refe­rencję do metody Proc jako parametr p. Obie zmienne, pktp zajmują tę samą pamięć, zawierają więc tę samą refe­rencję i odwołują się do jednego obiektu. Zatem zmiana wartości jego pól wewnątrz metody Proc jest widoczna poza nią, przez co wynikiem programu są liczby 50, -40.

Program w Visual C#

Rozszerzenie programu wyświetlania kalendarza miesię­cznego tak, by wyróżniał innym kolorem niedziele, święta i datę bieżącą, ułatwia zdefi­niowana w prze­strzeni System klasa Console i typ wylicze­niowy Console­Color. Klasa Console zawiera właści­wości Foreground­ColorBackground­Color umożli­wiające określenie koloru tekstu i tła konsoli. Z kolei typ ConsoleColor udostępnia nazwy i numery 16 kolorów przypomi­nających zestaw kolorów Borland C++:

Nazwa koloru Numer Opis Nazwa koloru Numer Opis
Black 0         – czarny DarkGray 8         – ciemnoszary
DarkBlue 1         – ciemnoniebieski Blue 9         – niebieski
DarkGreen 2         – ciemnozielony Green 10         – zielony
DarkCyan 3         – ciemnoturkusowy Cyan 11         – turkusowy
DarkRed 4         – ciemnoczerwony Red 12         – czerwony
DarkMagenta 5         – ciemnofioletowy Magenta 13         – fioletowy
DarkYellow 6         – ciemnożółty Yellow 14         – żółty
Gray 7         – szary White 15         – biały

Poniżej zaprezentowana jest pełna wersja programu wyświetla­jącego w oknie konsoli kalendarz miesię­czny, w którym niedziele i święta są wyróżniane kolorem czerwonym (Red), a data bieżąca kolorem fioletowym (Magenta). Program używa klasy Kalend (obli­czenia kalenda­rzowe) i klasy Swieta (wyzna­czanie dni świąte­cznych). W porówna­niu z wersją poprze­dnią w pro­gramie pojawiły się operacje manipulo­wania kolorami, zwiększona została również liczba parame­trów w wywo­łaniu i defi­nicji metody Miesiac. Oprócz numeru miesiąca m i numeru roku r do metody przeka­zywana jest data bieżąca dt typu DateTime i lista świąt sw typu Swieta. Lista świąt jest w metodzie Main wyzna­czana dla bieżą­cego roku tuż po uzyskaniu aktualnej daty, a potem każdo­razowo uaktual­niana, gdy przej­ście do następnego lub poprze­dniego miesiąca wiąże się ze zmianą numeru roku. Przeka­zanie parametru dt przez refe­rencję ma jedynie na celu zmniej­szenie narzutu pamięci. Zgodnie z dokumen­tacją firmy Microsoft DateTime jest bowiem strukturą o niemałej liczbie pól, czyli typem wartościowym, toteż zmienna dt zajmuje większy obszar pamięci niż referencja do niej.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Kalendarz.Greg;

namespace KMies3
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime dt = DateTime.Today;
            int m = dt.Month, r = dt.Year;
            Swieta sw = new Swieta(r);
            Console.ForegroundColor = ConsoleColor.Black;
            Console.BackgroundColor = ConsoleColor.White;
            Miesiąc(m, r, ref dt, sw);
            ConsoleKeyInfo c;
            while ((c = Console.ReadKey(true)).Key != ConsoleKey.Escape)
            {
                if (c.Key == ConsoleKey.DownArrow)
                {
                    if (++m > 12)
                    {
                        m = 1;
                        sw.Uzup(++r);
                    }
                    Miesiąc(m, r, ref dt, sw);
                }
                else if (c.Key == ConsoleKey.UpArrow)
                {
                    if (--m < 1)
                    {
                        m = 12;
                        sw.Uzup(--r);
                    }
                    Miesiąc(m, r, ref dt, sw);
                }
            }
        }

        static void Miesiąc(int m, int r, ref DateTime dt, Swieta sw)
        {
            Console.Clear();
            Console.CursorLeft = (17 - Mc[m].Length) / 2;
            Console.WriteLine("{0} {1}\n --------------------", Mc[m], r);
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Write(" Nd ");
            Console.ForegroundColor = ConsoleColor.Black;
            Console.WriteLine("Po Wt Śr Cz Pt So");
            int n = Kalend.Dtyg(1, m, r), max = Kalend.Dmax(m, r);
            Console.CursorLeft = 3 * n;
            for (int d = 1; d <= max; d++)
            {
                if (d == dt.Day && m == dt.Month && r == dt.Year)
                    Console.ForegroundColor = ConsoleColor.Magenta;
                else if (sw.Swieto(m, d) || Kalend.Dtyg(d, m, r) == 0)
                    Console.ForegroundColor = ConsoleColor.Red;
                Console.ForegroundColor = ConsoleColor.Black;
                Console.Write("{0,3}", d);
                if ((d + n) % 7 == 0)
                    Console.WriteLine();
            }
            Console.SetCursorPosition(2, 10);
            Console.Write("Menu: Up, Dn, Esc");
        }

        static readonly string[] Mc = {null,
            "Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec",
            "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"};
    }
}

A oto przykładowy wynik wykonania programu:


Opracowanie przykładu: październik/listopad 2018