wtorek, 13 listopada 2012

[C#|Visual Studio] C# Syntactic Sugars

Syntactic sugar to konstrukcja składniowa, charakterystyczna dla danego języka programowania, która ułatwia wykonanie pewnych operacji i poprawia czytelność. W C# aż roi się od specyficznych syntactic sugars, charakterystycznych tylko dla tego języka. Przykładem mogą być LINQ, które z jednej strony w minimalistyczny sposób pozwalają na operacje na kolekcjach, a z drugiej strony poprawiają czytelność.

W tym poście przedstawione zostaną niektóre, mniej popularne konstrukcje C#, które warto znać i które często mogą ułatwić życie.
  • default - słowo kluczowe, które zwraca wartość domyślną dla każdego typu (zawsze null dla typów referencyjnych, dla ValueTypes wartości te zdefiniowane są w specyfikacji języka

    public class defaultKeyword
    {
        public static T GetDefaultValue<T>()
        {
            return default(T);
        }   
    }

Console.WriteLine(defaultKeyword.GetDefaultValue<int>());
Console.WriteLine(defaultKeyword.GetDefaultValue<DateTime>());
//zwroci null, wypisze pusty string
Console.WriteLine(defaultKeyword.GetDefaultValue<defaultKeyword>());
  • volatile - obiekt może być modyfikowany przez wiele wątków jednocześnie. Obiekt taki nie jest poddawany optymalizacji kompilatora, który domyślnie zakłada, że dostęp do obiektu z pojedynczego wątku
  • implicit generics - kompilator w przypadku metod generycznych może wywnioskować typ po argumencie
public static void Print<T>(T instance)
{
    Console.WriteLine(instance);
}

//implicit generics
implicitGenerics.Print(new ASCIIEncoding());
//nie trzeba pisac
//implicitGenerics.Print<ASCIIEncoding>(new ASCIIEncoding());
  • @ - pozwala używać słów kluczowych C# w miejscach, w których normalnie jest to niedozwolone, np jako nazwy zmiennych

//@ character
string @string = "string";
Console.WriteLine(@string);
  • checked - używany do kontroli działań arytmetycznych, w których może nastąpić przepełnienie, gdy operację oznaczymy jako checked i przepełnienie wystąpi, rzucony zostanie wyjątek, gdy oznaczymy ją jako unchecked, wartość zostanie wyliczona przy uwzględnieniu "przekręcenia licznika"

//checked unchecked
short x = 32767;   // 32767 is the max value for short
short y = 32767;
try
{
    int z1 = checked((short)(x + y));   //will throw an OverflowException
    Console.WriteLine(z1);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

int z2 = unchecked((short)(x + y)); // will return -2
Console.WriteLine(z2);
  • static constructor - wywoływany automatycznie raz, przed tym, gdy pierwsza instancja jest utworzona, bądź przed stworzeniem pierwszej referencji do statycznego pola / propercji, gdy żadna z tych okoliczności nie zajdzie, konstruktor nie zostanie wywołany
  • params - umożliwia wywoływanie funkcji ze zmienną liczbą argumentów tego samego typu
public class paramsKeyword
{
    public static void Sum(params int[] values)
    {
        Console.WriteLine(values.Sum());
    }
}
//params
Console.WriteLine("params");
paramsKeyword.Sum(1,2,3);
paramsKeyword.Sum(1,2,3,4,5);
nnbvn

poniedziałek, 12 listopada 2012

[C#|Visual Studio] Struktury

Często niedocenianym przez wielu programistów .NET typem danych są struktury. Ich stosowanie zalecane jest wszędzie, gdzie mamy do czynienia z lekkimi, często stosowanymi obiektami, jak na przykład współrzędne, figura geometryczna, pole na szachownicy itd. Korzystanie z dużych kolekcji obiektów definiowanych jako klasy powoduje o wiele większe zużycie pamięci, niż gdybyśmy zdefiniowali te obiekty jako struktury. Poniżej kilka faktów z życia struktur w świecie .NET
  • może zawierać pola, metody, konstruktory
  • nie może dziedziczyć żadnego typu (poza ValueType, z którego dziedziczą niejawnie), może implementować interfejsy
  • w MSIL dodawane jest słowo kluczowe sealed, przez co nie można ze struktur dziedziczyć
  • nie mogą zawierać jawnego bezparametrowego konstruktora
  • nie muszą być inicjowane operatore new, np.
    public struct MyStruct
    {
        public int x;
        public int y;
    }

MyStruct b;
b.x = 3;
b.y = 2;
Console.WriteLine(b.x);
Console.WriteLine(b.y);
  • pól nie można inicjalizować w ciele struktury, a jedynie w konstruktorze zawierającym parametry, lub indywidualnie można nadawać wartość każdemu polu w kodzie proceduralnym
  • aby korzystać ze struktur jako typów referencyjnych należy przeprowadzić operację boxing, aby powrócić do ValueType, należy przeprowadzić unboxing

//boxing
object o = b;
//unboxing
b = (MyStruct) o;
  • boxing zachodzi również podczas rzutowania do interfejsów, z których dziedziczy dana struktura
  • obiekty klas powstają na stercie (w pamięci wolnej), przez co sprzątane są przez Garbage Collector w sposób niederministyczny, struktury natomiast tworzone są na stosie, przez co sprzątane są zaraz po wyjściu z bloku kodu, np po wykonaniu funkcji, w której je deklarujemy
  • inicjowanie instancji struktur operatorem new nie powoduje przeniesienia ich na stertę
  • jako argumenty funkcji możemy przekazywać struktury za pomocą sowa kluczowego ref

piątek, 9 listopada 2012

[C#|Visual Studio] Partial methods

Partial classes to konstrukcja składniowa, umożliwiająca definiowanie jednej klasy w wielu plikach. Może to być przydatne, by rozdzielić prace nad jedną klasą na kilku developerów, lub by odseparować np. kod generowany od kodu pisanego przez programistę, np. WinForms. Kompilator zbiera wszystkie pliki w całość i kompiluje jako jedną klasę. Wymagane jest, by pliki znajdowały się w jednym assembly..

O ile idea partial classes wydaje się w miarę jasna, o tyle temat partial methods może wielu programistom wydawać się niejasny.

Kilka informacji na temat partial methods
  • mogą znajdować się tylko w partial classes lub partial structs
  • jedna część klasy zawiera jedynie sygnaturę, w drugiej zawarta jest definicja
  • definicja metody jest opcjonalna (w przypadku jej braku kompilator pominie wszystkie wywołania)
  • dzięki powyższym cechą mogą być luźnie stosowane do wprowadzania opcjonalnych funkcjonalności
  • definicja musi rozpoczynać się od słowa kluczowego partial, a metoda może zwracać jedynie void
  • są to metody prywatne (niejawnie)
  • mogą być statyczne, generyczne
Przykład użycia:

Pierwsza część definicji klasy (np. wygenerowana przez automat)


using System;

namespace partial_methods
{
    //simulates automatically generated code
    public partial class ClassAB
    {
        //implemented
        partial void PrintYourName();
        //not implemented
        partial void PrintSurname();

        public void Print()
        {
            Console.WriteLine("Hello");
            PrintYourName();
            PrintSurname();
        }
    }
}

Druga część (np. definiowana przez użytkownika)


using System;

namespace partial_methods
{
    //simulates user defined class
    public partial class ClassAB
    {
        private string _name;

        public ClassAB(string s)
        {
            _name = s;
        }
        partial void PrintYourName()
        {
            Console.WriteLine(_name);
        }
    }
}

czwartek, 8 listopada 2012

[C#|Visual Studio] Konwersja typów

Nieco zapomnianymi i nieczęsto używanymi słowami kluczowymi C# są explicit i implicit. Służą one do zdefiniowania odpowiednio jawnej i niejawnej konwersji typów. Korzystanie z nich we własnych projektach może często zmniejszyć ilość kodu, poprawić jego przejrzystość. Gdy kod taki wystawiamy jednak jako API, programista może mieć problem z uzyskaniem informacji na temat tego, czy dane dwa typy są do siebie konwertowalne. 

explicit
  • wykonuje się tylko przy rzutowaniu newObject = (newType)object
  • gdy operator nie jest zdefiniowany, kompilator nie dopuści do konwersji
  • definiowany jako public static explicit operator

public class Celsius
{
    public float Value { get; set; }

    public static explicit operator Celsius(float v)
    {
        Console.WriteLine("explicit float -> Celsius");
        return new Celsius() { Value = v };
    }    
}

class Program
{
    static void Main(string[] args)
    {
        int deg_int = 20;

        Console.WriteLine();
        //explicit conversion
        Console.WriteLine("Celsius temp_Celsius =  (Celsius)deg_int;");
        Celsius temp_Celsius =  (Celsius)deg_int;
    }
}

implicit
  • operator niejawnej konersji, np przy przypisaniu elementu jednego typu do drugiego, przy wywołaniu metod itd.
  • kompilator nie dopuści do konwersji gdy nie zdefiniowaliśmy operatora
  • definiowany jako public static explicit operator

public class Celsius
{
    public float Value { get; set; }

    public static implicit operator double(Celsius v)
    {
        Console.WriteLine("implicit Celsius -> double");
        return v.Value;
    }

    public static void Print(double d)
    {
        Console.WriteLine(d);
    }

    public static void Put(int i)
    {
        Console.WriteLine(i);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Celsius temp_Celsius =  new Celsius(){Value = 20};

        //implicit conversion
        Console.WriteLine("double deg_double = temp_Celsius");
        double deg_double = temp_Celsius;

        Console.WriteLine("Celsius.Print(temp_Celsius)");
        Celsius.Print(temp_Celsius);

        Console.ReadLine();
    }
}

środa, 7 listopada 2012

[C#|Visual Studio] ConditionalAttribute

Podczas tworzenia oprogramowania często wykorzystujemy kod, nie mający żadnego pożytku po wdrożeniu rozwiązania. Może to być na przykład kod wypisujący coś na konsoli w celu sprawdzenia poprawności wykonania programu, lub właściwości służące jedynie do testów, pomiaru szybkości wykonania, wydajności itd.

Aby uniknąć sytuacji, w której musimy przy wdrożeniu usuwać spore ilości kodu, możemy skorzystać z atrybutu warunkowego. Oznaczone w nim metody będą wołane tylko w przypadku, gdy zdefiniowane jest makro z warunku, w przeciwnym przypadku kod programu przeskoczy wywołanie, a Visual Studio wyszarzy miejsce, w którym wołana jest taka funkcja.

Niespełnienie warunku i tak spowoduje wkompilowanie metod w assembly, co można łatwo sprawdzić przez mechanizm refleksji.


using System;
using D = System.Diagnostics;

namespace ConditionalAttribute
{
    public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }

        [D.Conditional("DEVELOP")]
        public void Print()
        {
            Console.WriteLine("Person");
        }

        public void FullName()
        {
            Console.WriteLine(Name + " " + Surname);
        }
    }
}


#define DEVELOP

using System;

namespace ConditionalAttribute
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person();
            person.Name = "John";
            person.Surname = "Doe";

            var type = typeof (Person);
            foreach (var m in type.GetMethods())
            {
                Console.WriteLine(m.Name);
            }
            Console.WriteLine("");

            person.Print();
            person.FullName();

            Console.ReadLine();
        }
    }
}

wtorek, 6 listopada 2012

[C#|Visual Studio] FlagsAttribute

Flagi bitowe to popularny sposób oznaczania pewnych obiektów przez enumeration w taki sposób, że mamy do dyspozycji dostępne wszystkie możliwe kombinacje (każdy z każdym) i każdej kombinacji przypada dokładnie jedna liczba. Aby uzyskać takie efekt, wartości kolejnych pól enuma oznaczamy przez kolejne potęgi dwójki. Potęga dwójki, to w zapisie bitowym jedynka na jednym miejscu i same zera w pozostałych miejscach. Po wykonaniu logicznej operacji OR na dwóch takich liczbach, różnych od siebie dostajemy dwie jedynki i same zera, co oznacza, że dany obiekt przynależy do dwóch zbiorów jednocześnie.

 .NET Framework wspiera flagi bitowe poprzez specjalny atrybut Flags. Korzystając z niego nie tylko oznaczamy, że dany typ wyliczeniowy jest flagą bitową. Dodatkowo wypisując wartości takiego typu dostajemy wylistowane, które zbiory taka wartość reprezentuje.

Warto pamiętać o ustawieniu wartości dla zer - będzie to wartość domyślna ustawiana przy starcie aplikacji przez CLR.

    class Program
    {
        static void Main(string[] args)
        {
            var list1 = new List<Roles>();
            var list2 = new List<Jobs>();
            for (var i = 0; i < 16; i++)
            {
                list1.Add((Roles)i);
                list2.Add((Jobs)i);
            }

            Console.WriteLine("Jobs - without flag");
            foreach (var job in list2)
            {
                Console.WriteLine(job);
            }

            Console.WriteLine("Roles - with flag");
            foreach (var role in list1)
            {
                Console.WriteLine(role);
            }
            Console.ReadLine();
        }
    }

    [System.Flags]
    public enum Roles
    {
        None = 0,
        Tester = 1,
        Developer = 2,
        Architect = 4,
        Manager = 8
    }

    public enum Jobs
    {
        None = 0,
        Tester = 1,
        Developer = 2,
        Architect = 4,
        Manager = 8
    } 
}

Na konsoli wypisane zostało:

Jobs - without flag
None
Tester
Developer
3
Architect
5
6
7
Manager
9
10
11
12
13
14
15
Roles - with flag
None
Tester
Developer
Tester, Developer
Architect
Tester, Architect
Developer, Architect
Tester, Developer, Architect
Manager
Tester, Manager
Developer, Manager
Tester, Developer, Manager
Architect, Manager
Tester, Architect, Manager
Developer, Architect, Manager
Tester, Developer, Architect, Manager

poniedziałek, 5 listopada 2012

[C#|Visual Studio] ThreadStaticAttribute

Tworząc programy wielowątkowe, w których korzystamy ze zmiennych statycznych, możemy ich używać na dwa sposoby. Zmienna statyczna może być globalna i współdzielona dla wszystkich wątków, albo też statyczna w obrębie jednego wątku. W tym przypadku dla każdego wątku tworzona jest osobna zmienna statyczna. Ustawienie tej funkcjonalności odbywa się poprzez atrybut ThreadStaticAttribute.

Na poniższym przykładzie można zaobserwować różnicę przy obu przypadkach.

namespace ThreadStaticAttribute
{
    public class MyClass
    {
        [System.ThreadStatic] public static int Count;
        public static int Count2;

        public void Count10()
        {
            for (int i = 0; i < 10; i++)
            {
                Count++;
                Count2++;
            }
        }

        public void Count20()
        {
            for (int i = 0; i < 20; i++)
            {
                Count++;
                Count2++;
            }
        }
    }
}

using System;
using System.Threading;

namespace ThreadStaticAttribute
{
    class Program
    {
        static void Main(string[] args)
        {
            var c1 = new MyClass();
            Thread t1 = new Thread(new ThreadStart(c1.Count10));
            Thread t2 = new Thread(new ThreadStart(c1.Count20));
            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();
            MyClass.Count++;
            MyClass.Count2++;
            Console.WriteLine(MyClass.Count);
            Console.WriteLine(MyClass.Count2);
            Console.ReadLine();
        }
    }
}

Zgodnie z przewidywaniami, konsola wyświetli wynik, jak poniżej.

1
31