Jasica.Net

C#, .Net, SQL i nie tylko

Sprawdzanie atrybytów

Istnieje kilka sposobów sprawdzania obecności atrybutów w .Net. Najpopularniejsze to metody IsDefined oraz GetCustomAttribute. Oczywiście drugą z nich można wykorzystać do sprawdzania atrybutu poprzez  porównanie zwracanej wartości z null'em. Poniżej prezentuję krótki kod porównujący wydajność obu rozwiązań.

    class Program
    {
        private static Type type = typeof(Test);

        private static Type attributeType = typeof(TestAttribute);

        private static void MeasureTime(Func<bool> action)
        {
            var watch = Stopwatch.StartNew();
            for(var i = 0; i < 1000; i++)
            {
                action ();
            }

            watch.Stop();

            Console.WriteLine(watch.ElapsedTicks);
        }

        static void Main()
        {
            RuntimeHelpers.PrepareMethod(typeof(Program).GetMethod("IsDefinded", BindingFlags.Public | BindingFlags.Static).MethodHandle);
            RuntimeHelpers.PrepareMethod(typeof(Program).GetMethod("GetCustomeAttributes", BindingFlags.Public | BindingFlags.Static).MethodHandle);

            MeasureTime(IsDefinded);
            MeasureTime(GetCustomeAttributes);
        }

        public static bool IsDefinded()
        {
            return type.IsDefined(attributeType, false);
        }

        public static bool GetCustomeAttributes()
        {
            return type.GetCustomAttribute(attributeType, false) != null;
        }
    }

    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
    public sealed class TestAttribute : Attribute
    {
        private readonly string positionalString;

        public TestAttribute(string positionalString)
        {
            this.positionalString = positionalString;
        }

        public string PositionalString
        {
            get
            {
                return positionalString;
            }
        }

    }


    [TestAttribute("SomeText")]
    public class Test
    {
        
    }

Uruchamiając kod na testowej maszynie uzyskałem rezultaty 3.548 oraz 10.607. Widać wyraźnie, że różnica jest znaczna, co w przypadku przeglądania całego assebly pod kątem obecności typów posiadających dany atrybut może wyraźnie skrócić czas operacji.

Na koniec chciałbym wspomnieć o jeszcze jednej metodzie pozwalającej pobrać atrybut -GetCustomAttributesData. Jest ona o tyle przydatna, gdyż nie powoduje utworzenia instancji atrybutu, a pozwala na wygodne sprawdzenie jakie parametry są przekazywane do konstruktora i właściwości, ale o tym innym razem

Dostęp do właściwości obiektów anonimowych bez refleksji

W tym wpisie pragnę zaprezentować kolejną ciekawą właściwość języka C#- tym razem odnosić się będzie ona do anonimowych obiektów.


Spójrzmy, więc na poniższy kod:


        static object GetAnonymousObject()
        {
            return new { Foo = 1, Bar = 2 };
        }

        static void Main(string[] args)
        {
            var obj = GetAnonymousObject();
            var something = ...;

            Console.WriteLine("Foo={0}, Bar={1}", something.Foo, something.Bar);
        }

Co należyt wstawić w miejsce kropek, aby kod kompilował się, a także nie powodował błędów w czasie wykonywania? Przykładowe rozwiązanie prezentuje poniżej.


        static void Main(string[] args)
        {
            var obj = GetAnonymousObject();
            var something = Cast(obj, new { Foo = 0, Bar = 0, Z = 0});

            Console.WriteLine("Foo={0}, Bar={1}", something.Foo, something.Bar);
        }

        static T Cast<T>(object obj, T tmpInstance)
        {
            return (T) obj;
        }


Analizując powyższy kod można zacząć się zastanawiać co pozwala mu działać nie powodując wyjątków rzutowania. Stoi za tym optymalizacja kompilatora, która dba o to by nie generować zbędnych typów anonimowych zawierających takie same zestawy właściwości. Warto, więc zauważyć, że dodanie lub usunięcie jakiegokolwiek pola z typu spowoduje rzucenie wyjątku nieprawidłowego rzutowania.

 

Jak wspomniałem na początku, jest to ciekawostka i należy podchodzić do niej ostrożnie. Zazwyczaj użycie standardowych mechanizmów refleksji czy obiektów dynamicznych lepszym rozwiązaniem.

Ukrywanie kodu przed narzędziami dbajacymi o jakość

W większych projektach często są wykorzystywane różne narzędzia generujące kod, które pozwalają zaoszczędzić czas programisty. Mogą one robić to poprzez tworzenie kodu źródłowego lub bezpośrednio bibliotek. Często jednak taki kod może być kłopotliwy dla narzędzi dbających o jakość.

Jeżeli kod jest generowany automatycznie może się okazać, że metryki typu Maintainability Index są stosunkowo niskie. W normalnym wypadku popsułoby to jedynie statystyki, ale jeżeli wykorzystujemy któreś z metryk w procesie ciągłej integracji, jako strażnika broniącego przed kodem niskiej jakości może się okazać, że takowy kod będzie problematyczny. Mankament ten można łatwo wyeliminować oznaczając wygenerowaną metodę atrybutem GeneratedCode. Dodatkowo jako parametr można przekazać nazwę narzędzia i wersję, które zostało wykorzystane do stworzenia kodu. Jeden atrybut i build server nie będzie sprawiał kłopotów. 

Analogicznie sprawa wygląda z wykluczeniem z pokrycia kodu- wystarczy dodać atrybut DebuggerNonUserCode. W tym przypadku jednak warto zastanowić się czy na pewno chcemy mieć kod w repozytorium, który nie jest weryfikowany przez żadne testy.

Style Cop można poprosić o ignorowanie kodu na kilka sposobów: poprzez dodanie w nagłówku pliku komentarza zawierającego "<auto-generated />" lub poprzez stworzenie regionu o nazwie zawierającej "generateg code".

Podsumowując czasami spotykanie niedogodności nie powinny być wymówką do zaniechania wdrażania narzędzi podnoszących jakość kodu, gdyż większość z nich można obejść.

Dynamiczny wybór wersji pomiędzy wersjami x86 oraz x64 natywnych bibliotek

Targetowanie projektu na architekturę x86 przy współpracy z natywnymi bibliotekami może nie być eleganckim rozwiązaniem, zwłaszcza jeśli dostępna jest w wersji 32 i 64 bitowej. Można tego uniknąć dokonując wyboru wersji biblioteki natywnej w czasie wykonywania programu.

Podobny problem w świecie kodu zarządzanego można by rozwiązać przy pomocy zdarzenia AssemblyResolve AppDomeny, która miałaby załadować kod. Co zrobić jednak, gdy biblioteka jest tylko wraperem na natywne wywołania? Należy w katalogu z plikami wykonywalnymi stworzyć dwa dodatkowe katalogi, w których znajdą się biblioteki skąpilowane na daną platformę(np. "\x64" i "\x32"). Natomiast czy aplikacja działa jako proces 32 czy 64 bitowy można sprawdzić przy pomocy właściwość Is64BitProcess klasy Environment. Na koniec należy jeszcze wskazać na podstawie wcześniej poczynionych kroków, gdzie system powinien szukać bibliotek. Najprościej to uczynić przy pomocy natywnej funkcji SetDllDirectory. Przykładowy kod może wygląda tak:

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetDllDirectory(string lpPathName);

        private static void Init()
        {
            var location = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            var type = Environment.Is64BitProcess ? "x64" : "x86";
            var path = Path.Combine(location, type);
            SetDllDirectory(path);
        }

Corupted State Exception

.Net w wersji 4.0 wprowadził pojęcie Corupted State Exception. W odróżnieniu od pozostałych wyjątków, nie są one wyłapywane przez bloki catch/finally. Takie zachowanie ma jak najbardziej sens, gdyż w większości sytuacji stanowią one sygnał, że w programie stało się coś złego i dalsze wykonywanie może nie mieć sensu. Występują one głównie przy współpracy z natywnymi bibliotekami. Poniższe wyjątki mogą być traktowane jako CSE:

  • STATUS_ACCESS_VIOLATION
  • STATUS_STACK_OVERFLOW
  • EXCEPTION_ILLEGAL_INSTRUCTION
  • EXCEPTION_IN_PAGE_ERROR
  • EXCEPTION_INVALID_DISPOSITION
  • EXCEPTION_NONCONTINUABLE_EXCEPTION
  • EXCEPTION_PRIV_INSTRUCTION
  • STATUS_UNWIND_CONSOLIDATE

Przeglądając tą listę możemy zauważyć, że w większości wypadków sugerują one błędy w kodzie natywnym. Poniższy kod C++ generuje błędne odwołanie do fragmentu pamięci, które posłuży jako przykład:

extern "C" __declspec(dllexport) 
int Add(int a, int b)
{
    int * t = 0;
    *t = 1;
    return a + b;
}

Kod tej funkcji w C# można wywołać stosując atrybut DllImport:

[DllImport("test.dll")]
public static extern int Add(int a, int b);

static void Main(string[] args)
{
     try
     {
         int result = Add(1, 2);
         Console.WriteLine(result);
     }
     catch (Exception ex)
     {
         Console.WriteLine("Cath: " + ex.Message + ex.StackTrace);
     }
     finally
     {
         Console.WriteLine("Finally");
     }
     Console.WriteLine("End");
}

Wynik działania powyższego kodu będzie różny w zależności od wybranej wersji CLR. W 4.0 nie wykona się zarówno blok catch jak i finally, natomiast w wcześniejszych wersjach wyjątek zostanie złapany, a program wykona się do końca. Wyjątki tego typu można wyłapywać CLR 4.0 o ile programista świadomie oznaczy metodę atrybutem HandleProcessCorruptedStateExceptions. Może to mieć znaczenie, gdy biblioteka zawiera dobrze znany błąd i nie da się go wyeliminować w danej chwili inaczej. Zachowanie to można wyłączyć także dla całego kodu, bez dokonywania jakikolwiek zmian w nim. Zachowanie kompatybilności wstecz można wymusić dodając wpis w pliku konfiguracyjnym:

<configuration>
    <runtime>
        <legacyCorruptedStateExceptionsPolicy enabled="true"/>
    </runtime>
</configuration>

Corupted State Exception chronią mniej świadomych programistów przed kontynuowaniem wykonywania kodu, gdy aplikacja znalazła się w złym stanie i dalsze operacje mogą prowadzić do utraty bądź uszkodzenia ważnych danych. Niestety, często w aplikacjach znajdują się zbyt ogólne bloki catch, łapiące wyjątki każdego typu, co zamiast zapewniać stabilność aplikacji, mogą prowadzić do nieoczekiwanych rezultatów. CAS chronią przed tego typu błędami i jednocześnie pozwalają wymusić wyłapywanie ich o ile zachodzi taka konieczność przy pomocy wspomnianego wcześniej atrybutu- HandleProcessCorruptedStateExceptions.

Szybki sposób na generowanie klas z JSON i XML

Zapewne nie raz spotkaliście się z potrzebą stworzenia klas odzwierciadlających pewne dane XML lub JSON. W takich przypadkach najlepiej posłużyć się odpowiednimi generatorami, by zaoszczędzić sporo czasu na tworzeniu kodu.

Pomocny może być dodatek Web Essentials 2012. Dzięki niemu teraz zawsze będziemy mieli taki generator pod ręką- dosłownie. Wystarczy kliknąć prawym przyciskiem myszy odpowiednią opcje z menu kontekstowego i JSON ze schowa zamieni się w piękną klasę:

 

W analogiczny sposób można też obsłużyć XMLa.

Polecam zapoznanie się z pozostałymi możliwościami dodatku.

MSDeploy(aka WebDeploy) - problem po aktualizacji do .Net 4.5

Migracja do .Net 4.5, mimo iż miał być bezproblemowa, czasem stwarza problemy. Jednym z narzędzi jakie nie chciało od razu współpracować z .Net 4.5 jest MSDeploy(aka WebDeploy).

Po migracji projektu do .Net 4.5 MSDeploy podczas publikacji paczki na IIS, zgłaszał błąd:

Error Code: 51
More Information: The application pool that you are trying to use has the
'managedRuntimeVersion' property set to 'v4.0'. This application requires 'v4.5'.
Error count: 1.

Mało eleganckim obejściem jest oszukanie MSDeploy'a poprzez zmianę wersji .Net przed publikacją przy pomocy przełącznika preSync:

-preSync:runCommand="%systemroot%\system32\inetsrv\APPCMD stop apppool P  
 & %systemroot%\system32\inetsrv\APPCMD set apppool P /managedRuntimeVersion:v4.5"

Wykonanie tego polecenia wprowadzi zmiany w ustawieniach puli, a nawet nowa wersja frameworka będzie dostępna do wyboru na liście:

 

Oczywiście, tak ustawiona pula nie będzie działać poprawnie, więc należy przywrócić ustawienia pierwotne, prosząc MSDeploy'a by wykonał na zakończenie porządki:

-postSync:runCommand="%systemroot%\system32\inetsrv\APPCMD set apppool P /managedRuntimeVersion:v4.0  
 & %systemroot%\system32\inetsrv\APPCMD start apppool P

Zna ktoś inne rozwiązanie tego problemu?

SignalR - czyli jak łatwo przesłać informacje o zdarzeniu do klienta

Cykliczne odpytywanie serwera może być problematyczne i mało wydajne. Istnieje jednak biblioteka SignalR, która znacząco upraszcza przesyłanie informacji o zdarzeniach z serwera do przeglądarki.

Biblioteka bardzo dużo odwala za programistę- przykładowy czat można napisać używając zaledwie kilkudziesięciu lini kodu. Zachęcam do zapoznania się przykładami.

Warto też zauważyć, że biblioteka nie ogranicza nas co do stosowanych technologi- klientem nie musi być przeglądarka(może to być dowolna aplikacja .Net'owa), a serwer nie musi wykorzystywać Asp.Net'owych handlerów do działania- możemy hostować serwer w dowolnej aplikacji, choćby w konsoli.

Jedyną wadą biblioteki jest to, że nie wspiera standardowo skalowania na wiele węzłów, a tym samym zwiększonej niezawodności. Można to jednak uzyskać poprzez wykorzystanie Windows Azure Service Bus, przez co sam projekt wydaje się być obiecujący. Wsparcie biblioteki nie ogranicza się tylko do okolic platformy .Net. Na githubie można znaleźć biblioteki mający na celu dodanie wsparcia dla iOS i Mac, a także Androida.

Biblioteka posiada całkiem spore możliwości, a na dodatek całkiem silne wsparcie społeczności. Uważam, że z tych wszystkich powodów warto poświecić jej chwile i zapoznać się z nią.

Tworzenie obiektów bez wykonywania konstruktora

Tworzenie obiektów w .Net jest utożsamiane z zarezerwowaniem dla nich pewnej pamięci, a następnie wykonaniem konstruktora(domyślnego lub parametryzowanego). Jednak w pewnych przypadkach wykonywanie kodu zawartego w konstruktorze może być niepożądane. Istnieje jednak sposób by tego uniknąć.

Brak angażowania logiki w konstruktorze jest zazwyczaj pożądany podczas odtwarzania obiektu np. po odczycie danych z dysku twardego lub danych przesłanych przez sieć. Dodatkowo, jeśli obiekt posiada bezparametryczny konstruktor utworzenie obiektu danego typu przy pomocy refleksji nie jest trudne. Sprawa staje się bardziej skomplikowana jeśli obiekt wymaga do inicjalizacji innych danych. W takim przypadku z pomocą przychodzi statyczna metoda FormatterServices.GetUninitializedObject, która jako jedyny parametr przyjmuje typ obiektu jaki chcemy utworzyć. Należy uważać z jej użyciem, gdyż tworzy ona obiekt, którego przestrzeń w pamięci jest wypełniona samymi zerami. Może implikować stan obiektu, który nie jest dla niego dozwolony i może objawiać się niepożądanym działaniem . Taki obiekt należy jak najszybciej wypełnić danymi przed jego wykorzystaniem. Przykład użycia:

    public class Foo
    {
        private int bar;
        private int baz;

        public Foo(int bar)
        {
            this.bar = bar;
            this.baz = 8;
        }

        public override string ToString()
        {
            return string.Format("bar={0}, baz={1}", this.bar, baz);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var test = (Foo)FormatterServices.GetUninitializedObject(typeof(Foo));
            Console.WriteLine(test);
        } 
    }

Na ekranie, zgodnie z oczekiwaniami, zostanie wypisana informacja, z której wynika, że obiekt nie został zainicjalizowany- "bar=0, baz=0".

Samo działanie metody jest interesujące- jak obchodzi ona taki element języka jak wywołanie konstruktora? Dekompilując jej kod nie dowiemy się dużo:

    [SecurityCritical]
    public static object GetUninitializedObject(Type type)
    {
      if (type == null)
        throw new ArgumentNullException("type");
      if (type is RuntimeType)
        return FormatterServices.nativeGetUninitializedObject((RuntimeType) type);
      throw new SerializationException(Environment.GetResourceString(
        "Serialization_InvalidType", 
        new object[1] { (object) type.ToString() }));
    }

    [MethodImpl(MethodImplOptions.InternalCall)]
    [SecurityCritical]
    private static extern object nativeGetUninitializedObject(RuntimeType type);

Oznaczenie metody jako extern wraz z MethodImplOptions.InternalCall mówi, że metoda jest zaimplementowana wewnątrz CLR. Jeśli chcemy pogrzebać głębiej zawsze można zerknąć na kod SSCLI

FCIMPL1(Object*, ReflectionSerialization::GetUninitializedObject, ReflectClassBaseObject* objTypeUNSAFE) {
    CONTRACTL {
        THROWS;
        DISABLED(GC_TRIGGERS);
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }
    CONTRACTL_END;
    
    OBJECTREF           retVal  = NULL;
    REFLECTCLASSBASEREF objType = (REFLECTCLASSBASEREF) objTypeUNSAFE;

    HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_RETURNOBJ);

    if (objType == NULL) {
        COMPlusThrowArgumentNull(L"type", L"ArgumentNull_Type");
    }

    TypeHandle type = objType->GetType();

    // Don't allow arrays, pointers, byrefs or function pointers.
    if (!type.IsUnsharedMT())
        COMPlusThrow(kArgumentException, L"Argument_InvalidValue");

    MethodTable *pMT = type.GetMethodTable();
    PREFIX_ASSUME(pMT != NULL);

    //We don't allow unitialized strings.
    if (pMT == g_pStringClass) {
        COMPlusThrow(kArgumentException, L"Argument_NoUninitializedStrings");
    }

    // if this is an abstract class or an interface type then we will
    //  fail this
    if (pMT->IsAbstract()) {
        COMPlusThrow(kMemberAccessException,L"Acc_CreateAbst");
    }
    else if (pMT->ContainsGenericVariables()) {
        COMPlusThrow(kMemberAccessException,L"Acc_CreateGeneric");
    }

    // Never allow the allocation of an unitialized ContextBoundObject derived type, these must always be created with a paired
    // transparent proxy or the jit will get confused.
    if (pMT->IsContextful())
        COMPlusThrow(kNotSupportedException, L"NotSupported_ManagedActivation");

    // If it is a nullable, return the underlying type instead.  
    if (Nullable::IsNullableType(pMT)) 
        pMT = pMT->GetInstantiation()[0].GetMethodTable();
 
    retVal = pMT->Allocate();

    HELPER_METHOD_FRAME_END();
    return OBJECTREFToObject(retVal);
}
FCIMPLEND

Jest tu sprawdzana cała masa warunków, aż ostatecznie alokowana jest po prostu pamięć - nie ma tu żadnego wołania konstruktora.

 

 

 

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.