Jasica.Net

C#, .Net, SQL i nie tylko

Nieudokumentowane słowa kluczowe C#

Istnieją 4 słowa kluczowe, o których istnieniu wie niewielu programistów C#: __arglist, __refvalue, __makeref, __reftype. Stanowią one jednak ciekawostkę, o której istnieniu warto mieć pojęcie. Oficjalna dokumentacja(C# Keywords) nie wspomina o nich. Można jednak szybko przekonać się o ich istnieniu, gdyż Visual Studio podświetla je podobnie jak inne słowa kluczowe( domyślnie na niebiesko). Najprawdopodobniej powstały one by dać dostęp do instrukcji CLI takich jak: arglist, refanyval, mkanyref, refanytype, o których mowa w specyfikacji CLI.

Pierwsze słowo kluczowe- __arglist, daje  możliwość, podobnie jak słówko kluczowe params, przekazania wielu parametrów do metody. Niekoniecznie wszystkie muszą być tego samego typu. Poniżej znajduje się przykład użycia:

public static void Main(string[] args)
{
    PrintList(__arglist(1, 2, 3, "Four", 5, "Six"));
}

public static void PrintList(__arglist)
{
    ArgIterator iterator = new ArgIterator(__arglist);
    Console.WriteLine("Count: {0}", iterator.GetRemainingCount());
    Console.WriteLine("Items:");
    object current = null;
    while (iterator.GetRemainingCount() > 0)
    {
        TypedReference item = iterator.GetNextArg();
        Console.WriteLine(TypedReference.ToObject(item));
    }
}

Użycie słowa kluczowego __arglist, w stosunku do params nie jest wcale prostsze, czy też czytelniejsze. Należy zauważyć, że dostęp do argumentów daje dopiero specjalny typ- ArgIterator. Dwie jego najważniejsze metody- GetNextArg i GetRemainingCount, pozwalają pobrać kolejny element z listy i sprawdzić ile jeszcze elementów zostało do przejrzenia. Dodatkowo dostęp do kolejnych elementów listy jest "utrudniony", gdyż zwracane są one jako obiekt typu TypedReference. Jednak po co utrudniać sobie aż tak tworzenie kodu? Odpowiedź nasuwa mi się tylko jedna- ułatwienie współpracy z językami takimi jak C/C++ wspierającymi zmienną liczbę argumentów poprzez operator wielokropka(...). Po więcej na temat samego operatora odsyłam do Wikipedi.

Słówka kluczowe __refvalue oraz __reftype służą do współpracy z obiektami typu TypedReference. Pozwalają na pobranie wartości oraz jej typu. Wypisywanie wartości elementu listy przy pomocy tych słów kluczowych mogłoby wyglądać na przykład tak:

public static void PrintTypeReference(TypedReference item)
{
    Type type = __reftype(item);
    if(type == typeof(int))
    {
        int number = __refvalue(item, int);
        Console.WriteLine("Type: {0}, Number: {1}", type, number);
    }
    else
    {
        string text = __refvalue( item, string);
        Console.WriteLine("Type: {0}, Text: {1}", type, text);
    }
}

Nic nie stoi jednak na przeszkodzie, by skorzystać z statycznych metod struktury TypedReference, które pozwalają pozbyć się "niewygodnych" słów kluczowych. Powyższy kod przetłumaczony wraz z ich użyciem mógłby wyglądać tak:

public static void PrintTypeReference(TypedReference item)
{
    Type type = TypedReference.GetTargetType(item);
    if (type == typeof(int))
    {
        int number = (int)TypedReference.ToObject(item);
        Console.WriteLine("Type: {0}, Number: {1}", type, number);
    }
    else
    {
        string value = (string)TypedReference.ToObject(item);
        Console.WriteLine("Type: {0}, Text: {1}", type, value);
    }
}

Pozostaje jeszcze jedno słówko kluczowe- __makeref. Służy ono do tworzenia obiektów typu TypedReference. Przykładowo wypisanie wartości obiektu może wyglądać następująco:

int i = 8;
PrintTypeReference(__makeref(i));

Należy także pamiętać, że TypedRefenece przechowywuje zarówno typ jak i zarządzany wskaźnik, więc modyfikacja obiektu typy TypedReference może mieć odzwierciedlenie w innym fragmencie kodu. Przykładowo kod inkrementujący zmienną może wyglądać następująco:

public static void Main(string[] args)
{
    int i = 8;
    Inc(__makeref(i));
    Console.WriteLine(i);
}

public static void Inc(TypedReference value)
{
    __refvalue(value,int)++;
}

Oczywiście powyższy kod wypisze 9 na konsoli.

Na koniec jeszcze jeden ciekawy przykład użycia __makeref wraz z refleksją:

public struct Point
{
    public int X;
    public int Y;

    public override string ToString()
    {
        return X + ", " + Y;
    }
}

public static void Main(string[] args)
{
    Point point = new Point() { X = 1, Y = 2 };
    Console.WriteLine(point);

    FieldInfo fieldInfo = typeof(Point).GetField("X");
    fieldInfo.SetValue(point, 3);
    Console.WriteLine(point);

    fieldInfo.SetValueDirect(__makeref(point), 4);
    Console.WriteLine(point);
}

Oczekiwalibyśmy od tego kodu, że w jako druga lina wyświetlone zostanie "3, 2". Niestety sygnatura SetValue jako pierwszy parametr posiada parametr typu object, więc dojdzie tu do niechcianego boxingu, a oczekiwana zmienna point nie zostanie zmodyfikowana. Problem ten rozwiązuje dopiero użycie SetValueDirect.

Nasuwa się jeszcze pytanie: używać, czy nie używać? Skoro są to nieudokumentowane elementy języka istnieje prawdopodobieństwo, że mogą zniknąć wraz z kolejną wersją kompilatora. Z drugiej jednak strony twórcy tego oprogramowaina mogą nie chcieć utracić kompatybilności z już istniejącym kodem, który potencjalnie może z nich kożystać. Generalnie przedstawiam to jako ciekawostkę i nie polecam używania w produkcyjnym kodzie.

Nie rób tego w domu pracy.

Dodaj komentarz

Loading