Przedostatnią rzeczą o jakiej chciałbym wam powiedzieć to funkcje lokalne. Jest to trochę zakręcony temat. Więc zobaczymy jak mi to pójdzie :)

Z cyklu nowości w C# 7, do tej pory ukazały się artykuły:

  1. C# 7: Tuples
  2. C# 7: Pattern Matching
  3. C# 7: out i ref
  4. C# 7: Funkcje lokalne
  5. C# 7: Lukier składniowy

Side node: wszystkie przykłady zostały przetestowane na VS 2017 RC.

Czasami jak piszemy kod to zauważamy, że jedna funkcja wykonuje pewną operację kilka razy. Można więc ten kod wyciągnąć przed nawias tworząc osobą funkcję – prywatną.

Jednak jak mamy kilka takich miejsc, to mogą one nam jednak przeszkadzać niż pomagać. Ja osobiście w takich sytuacjach korzystałem z Action/Func – tworzę lokalną implementację i następnie się do niej odwoływałem, głównie ze względu na closure (dostęp do zmiennych lokalnych).

public GeneralResponse<Person> Get()
{
    Func<GeneralResponse<Person>, GeneralResponse<Person>> exit = returnValue =>
    {
        if (returnValue.Success)
        {
            Console.WriteLine("TRACE: Detailed trace");
        }
        else
        {
            Console.WriteLine("WARN: Not found!");
        }

        return returnValue;
    };

    Func<GeneralResponse<Person>> notFound = () =>
    {
        var returnValue = new GeneralResponse<Person>();

        returnValue.Success = false;
        returnValue.Type = GeneralErrorType.NotFound;

        return exit(returnValue);
    };

    var model = "test";

    if (model == null)
    {
        return notFound();
    }

    var result = new GeneralResponse<Person>();
    result.Success = true;

    return exit(result);
}

To samo, mogliśmy robić w JavaScript:

function f () {
    function z () {
        return "z";
    }
    console.log(z());
}

W C# zaś od wersji 7 możemy tak zmodyfikować przykład z Func:

public GeneralResponse<Person> Get()
{
    var model = "test";

    if (model == null)
    {
        return notFound();
    }

    var result = new GeneralResponse<Person>();
    result.Success = true;

    return exit(result);

    GeneralResponse<Person> notFound()
    {
        var returnValue = new GeneralResponse<Person>();
        returnValue.Success = false;
        returnValue.Type = GeneralErrorType.NotFound;
        return exit(returnValue);
    }

    GeneralResponse<Person> exit(GeneralResponse<Person> response)
    {
        if (response.Success)
        {
            Console.WriteLine("TRACE: Detailed trace");
        }
        else
        {
            Console.WriteLine("WARN: Not found!");
        }
        
        return response;
    }
}

Podsumowanie

Czy to jest coś niesamowitego na co czekałem? NIE. Czy będę z tego korzystał? Jest przyjemniejsze ciutkę od Action/Func. Ale jakoś nie rzucę się na to od razu. Może trochę mi to uporządkuje kod? Fajne, jest to, że closure tutaj też działa i możemy wykorzystać zmienne lokalne w funkcji.

7 KOMENTARZE

  1. Cześć Gutek!

    Generalnie dostajemy to samo, tylko z ładniejszą składnią (à la JS/Python/etc.). Ciekawe czy funkcje w C# 7 stają się przy okazji First Class Object i możliwe będzie odwołanie się do zawartości funkcji z zewnątrz tak jak to jest w Pythonie (innymi słowy, czy M$ chce zaprosić do siebie fanów takiego podejścia umożliwiając jego stosowanie w C#).

    Mam na myśli coś takiego:

    void f(){
    int g(int x){ return x*x;}
    […]
    }
    int z = f.g(4);

    • @MN, “odwołanie się do zawartości funkcji z zewnątrz” why would you do that?
      Nie hejce, po prostu ciekaw :)

      • bo jak jest możliwość to czemu by nie? ;) ale, tak, niezłe pytanie :) dlaczego – czysta ciekawość.

        zaś @MN – jak znajdę chwilę czasu to zobaczę już z czystej ciekawości czy się da:)

      • Smutne czasy nastały, żeby człowiek musiał zaznaczać w co drugiej wypowiedzi…

        A co do twojego/waszego pytania, to szczerze mówiąc, trudno odpowiedzieć ;/. Taką możliwość znam z Pythona, gdzie funkcje oraz metody klas są obiektami, a także z C++ pod postacią klas z przeładowanym operatorem wywołania funkcji (co de facto wygląda jak funckje w Pythonie ), ale nie zastanawiałem się nigdy nad sensownością tego podejścia, a co więcej – za krótko w tym siedzę, aby się wypowiadać :D. Aż się muszę nad tym głębiej zastanowić…

        Tak na gorącą przychodzi mi do głowy jeden pomysł:

        Mamy dodać do naszej aplikacji nową prostą, acz konfigurowalną w czasie działania funckjonalność. Pierwsza myśl: ‘ trzeba stworzyć klasę-serwis, która ma jakąś metodę, która jest konfigurowana poprzez publiczne atrybuty klasy.’. W efekcie dostajemy klasę, z kosntruktorem, paroma polami klasy i naszą metodę, która to albo używa konfiguracji z pól klasy, albo rzuca wyjatkiem/ma domyślne zachowanie, gdy config jest pusty/błędny (tak wymyślam sobie, może mnie fantazja ponieść…).

        Mając do dyspozycji funkcje będące czymś na pograniczu funkcji i obiektu, moglibyśmy zrobić to pisząc tylko i wylącznie naszą metodę, bez całej otoczki w postaci klasy, itd.

        type function(input)
        {
            string printConfig()
            {
                return this.config;
            }
            if(this.config == null)
            {
                this.config = defaultConfig;
            }
            return input*something*this.config;
        }
        

        I potem używamy tego tak:

        x1 = function(5);
        function.printConfig()
        function.config = 2;
        x2 = function(5);
        

        Feature wykonany, config jest blisko samej funkcji i finalnie mniej kodu, więc nie ma powodu do narzekań chyba…
        No i kurczę, wychodzę na ewangelistę od programowania funkcyjnego… A dopiero co mówiłem, że FP nie jest lekiem na całe zło…

        Dobra, wiem, przykład trochę na siłe, ale majac na uwadze, że w naszym fachu ‘nie ma nigdy tego jedynego, dobrego rozwiązania, są tylko złe i gorsze’ a wszystko zależy od kontekstu, to może miałoby to jakiś sens :D. W końcu sprzedawanie noży nie oznacza, że wszyscy będą nimi pozbywać się denerwujących sąsiadów, czasem może uda się tym nożem uratować jakiegoś samobójcę-wisielca ;).

        Dla ciekawskich: http://stackoverflow.com/questions/338101/python-function-attributes-uses-and-abuses

  2. aa w ten sposób – by by funkcje były “publiczne” a nie ograniczone do scope w którym zostały stworzone. to w C# 7 nie jest to możliwe – są traktowane jako “prywatne”, czyli nie możemy wywołać Get.notFound().

    w C# te funkcje zamieniane są na internal static:

    typeof(Person).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
    {System.Reflection.MethodInfo[2]}
        [0]: {TestApp.GeneralResponse`1[TestApp.Person] g__notFound3_0(<>c__DisplayClass3_0 ByRef)}
        [1]: {TestApp.GeneralResponse`1[TestApp.Person] g__exit3_1(TestApp.GeneralResponse`1[TestApp.Person])}
    
    • Nonono, właśnie o to mi chodziło :). Czyli hipotetycznie, gdyby nie access modifier to byśmy mieli do tego dostęp… Wniosek – jest tak samo jak w Pythonie, tyle, że w Pythonie nie ma ograniczania zakresu: “wszystko jest publiczne, gdyż wszyscy jesteśmy tutaj dorośli’, jak to mawiają ludzie odpowiedzialni za Pythona. Dzięki za sprawdzenie bebechów!

Comments are closed.