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

niedziela, 4 listopada 2012

[C#|Visual Studio] DebuggerBrowsableAttribute

Kolejnym atrubutem pomocnym podczas debugowania programu jest DebuggerBrowsable znajdujący się, podobnie jak DebuggerDisplay w System.Diagnostics. Atrybut ten okazuje się pomocny, gdy przeglądamy obiekty składające się z dużej ilości properties, a chcemy śledzić stan jedynie niektórych z nich, bądź wyświetlać je w sposób łatwiejszy do podglądu. Nad każdym property można umieścić atrybut DebuggerBrowsable, który w konstruktorze przyjmuje jedną z trzech opcji.
  • Never - pole lub property nie będzie widoczne po rozwinięciu obiektu w zakładce Locals
  • Collapsed - zachowanie domyślne, obiekt widoczny, ale nie rozwinięty
  • RootHidden - w przypadku wyświetlania kolekcji, jej elementy są widoczne poziom wyżej, bez wyświetlania jej roota. Np. jeśli mamy listę napisów jako property pewnej klasy, to przy ustawieniu RootHidden, wszystkie napisy będą widoczne jako properties tej klasy.
Zasadę działania ilustruje także poniższy przykład.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using D = System.Diagnostics;

namespace DebuggerBrowsableAttribute
{
    public class SampleClass
    {
        [D.DebuggerBrowsable(D.DebuggerBrowsableState.RootHidden)]
        public List<int> RootHidden { get; set; }

        [D.DebuggerBrowsable(D.DebuggerBrowsableState.Collapsed)]
        public int Collapsed { get; set; }

        [D.DebuggerBrowsable(D.DebuggerBrowsableState.Never)]
        public int Never { get; set; }

        public SampleClass()
        {
            Random r = new Random();
            RootHidden = new List<int>();
            RootHidden.Add(r.Next(100));
            RootHidden.Add(r.Next(100));
            RootHidden.Add(r.Next(100));
            Collapsed = r.Next(100);
            Never = r.Next(100);
        }
    }
}


sobota, 3 listopada 2012

[C#|Visual Studio] DebuggerDisplayAttribute

DebuggerDisplayAttribute można znaleźć w System.Diagnostics. Atrybut ten służy do kontroli sposobu, w jaki wyświetlamy daną zmienną w oknie zmiennych debuggera. Przydaje się to np. do klas zawierających kilka properties, które chcemy podejrzeć. Jako argument konstruktora podajemy string z możliwością używania wyrażeń umieszczonych w nawiasach klamrowych {}. W wyrażeniach tych można na przykład podawać nazwy properties, przeprowadzać na nich operacje, czy wywoływać funkcje. Należy jednak pamiętać, że operacja wykona się przy każdym odświeżeniu debuggera, przez co często zmusimy komputer do sporej ilości niekoniecznie potrzebnych obliczeń.

using System;
using D = System.Diagnostics;

namespace DebuggerDisplayAttribute
{
    [D.DebuggerDisplay("Sample class: First: {First}, Second: {Second}, Sum: {Sum()}")]
    public class SampleClass
    {
        public int First { get; set; }
        public int Second { get; set; }

        public SampleClass()
        {
            Random r = new Random();
            First = r.Next(100);
            Second = r.Next(100);
        }

        public int Sum()
        {
            return First + Second;
        }
    }
}

Podczas zatrzymania pracy programu w trybie debug, z automatu otrzymamy:


piątek, 2 listopada 2012

[C#|Visual Studio] DefaultValueAttribute

Jednym z atrybutów, jakie udostępnia System.ComponentModel jest DefaultValueAttribute. Wbrew pozorom podanie wartości jako argument konstruktora nie wystarczy, aby ustawić domyślną wartość property. Dokumentacja MSDN jasno mówi, że programista musi sam ustawić domyślne wartości, np. w konstruktorze. Aby wykorzystać możliwości DefaultValueAttribute można skorzystać z mechanizmu reflection.

Zatem poniższy kod, pomimo swojej przejrzystości nie wystarczy, aby ustawić domyślne wartości dla properties.

using System;
using CM = System.ComponentModel;

namespace DefaultValueAttribute
{
    public class MySimpleClass
    {
        [CM.DefaultValue(true)]
        public bool MyBoolProperty { get; set; }

        [CM.DefaultValue(8)]
        public int MyIntProperty { get; set; }

        [CM.DefaultValue("Default text")]
        public string MyStringProperty { get; set; }
    }
}

Aby móc skorzystać z możliwości ustawiania wartości domyślnych poprzez atrybuty, wystarczy skorzystać z poniższej ExtensionMethod.

public static class Extensions
    {
        public static void ApplyDefaultValues(this object obj)
        {
             foreach (CM.PropertyDescriptor prop in CM.TypeDescriptor.GetProperties(obj))
             {
                 var attr = (CM.DefaultValueAttribute) prop.Attributes[typeof (CM.DefaultValueAttribute)];
                 {
                     prop.SetValue(obj, attr.Value);
                 }
             }
        }
    }

Wywoływać ją można np. w konstruktorze.