\

Školení UML, OOP, Návrhové vzory, .Net Framework, compact .Net Framework, ASP.NET


 Friday, May 09, 2008
LINQ II - přetypovávání i vnořených anonymních datových typů z jiné assembly

V předchozím spotu jsem byl schopen pracovat s anonymními datovými typy, i když byly dotazy a výsledné sady dat vytvořeny v jiné assembly. Odstranění vrozené xenofobie v praxi.:)

Náš kód ale vygeneruje výjimku, jestliže anonymní datový typ z jiné assembly obsahuje další vnořené anonymní datové typy jako v následujícím upraveném příkladu. Vlastnost InnerAT vrací další anonymní datový typ, který  pro zajímavost obsahuje odkaz ještě na další anonymní datový typ.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LINQTEST
{
    public class TestAT
    {
        public static object GetResult()
        {
            string[] rows = { "Toyota", "Lexus", "Audi" };

            var test = from row in rows
                       select new
                       {
                           FirstLetter = row[0],
                           Index = 110,
                           Original = row,
                           InnerAT = new { X = row[1], B = new {A=1}}
                       };
            
            return test;

        }
    }
}

Řešení spočívá v úpravě extenzí a to tak, že přidáme privátní metodu GetTypeInstance a přeneseme do ní většinu kódu z extenze ToAnonymousType. Metoda GetTypeInstance při neshodě datového typu očekávaného parametrem "našeho - v naší assembly dostupného" konstruktoru anonymního datového typu a datového typu vlastnosti anonymního datového typu z "cizí" assembly rekurzivně přenese data z "cizího" anonymního datového typu do "našeho".

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using System.Collections;
using LINQTEST;

namespace LINQAnonymous
{
    /// <summary>
    /// Rozšíření pro LINQ
    /// </summary>
    static class RSLinqExtensions
    {

        /// <summary>
        /// Metoda přetypuje objekt na anonymní typ, jehož struktura byla předána v parametru <paramref name="prototype"/>
        /// </summary>
        /// <typeparam name="T">Kompilátorem odvozený anonymní typ</typeparam>
        /// <param name="prototype">Prototyp se strukturou anonymního typu</param>
        /// <returns>Instanci anonymního typu, nebo null, jestliže konverzi nelze provést</returns>
        /// <remarks>Metoda se pokusí převést data z různých assembly</remarks>
        public static T ToAnonymousType<T>(this object obj, T prototype)
                                        where T: class
        {
            
            
            T atiObj = obj as T;
            
            if (atiObj == null)
            {
                
                atiObj = GetTypeInstance(obj, prototype.GetType()) as T;
               
            }
                                                     
            return (atiObj);
        }
    

        private static object GetTypeInstance(object obj, Type expected)
        {
            object atiObj = null;

            ConstructorInfo constructorInfo = expected.GetConstructors()[0];
                
                if (constructorInfo == null)
                {
                    return null;
                }

                ParameterInfo[] paramInfos = constructorInfo.GetParameters();                
                PropertyInfo[] origProperties = obj.GetType().GetProperties();
                

                if (paramInfos.Count() != origProperties.Count())
                {
                    return null;
                }

                object[] paramArgs = new object[paramInfos.Count()];


                for (int i = 0; i < paramArgs.Length; i++)
                {
                    PropertyInfo origProperty = origProperties.Where(prop => prop.Name == paramInfos[i].Name).FirstOrDefault();

                    if (origProperty == null)
                    {
                        return null;
                    }

                    object val = origProperty.GetValue(obj, null);
                    if (origProperty.PropertyType != paramInfos[i].ParameterType)
                    {
                        val = GetTypeInstance(val, paramInfos[i].ParameterType);
                    }

                    paramArgs[i] = val;
                }

                atiObj = constructorInfo.Invoke(paramArgs);
                return atiObj;
        }
        /// <summary>
        /// Metoda vrátí
        /// </summary>
        /// <typeparam name="T">Kompilátorem odvozený anonymní typ</typeparam>
        /// <param name="prototype">Prototyp se strukturou anonymního typu</param>
        /// <returns>List instancí anonymního typu, nebo null, jestliže konverzi nelze provést</returns>
        /// <remarks>Metoda se pokusí převést data z různých assembly</remarks>
        public static List<T> CastToList<T>(this object obj, T prototype)
                                 where T : class
        {
            List<T> list = new List<T>();
            IEnumerable<T> enumerable = obj as IEnumerable<T>;

            if (enumerable != null)
            {
                list.AddRange(enumerable);
            }
            else
            {
                    IEnumerable enumObjects = obj as IEnumerable;
                    if (enumObjects == null)
                    {
                        return null;
                    }
                    
                foreach (object enumObject in enumObjects)
                    {
                        T currObject = ToAnonymousType(enumObject, prototype);
                        if (currObject == null)
                        {
                            //K čistění listu by neměl být důvod, ale garantujeme, že nevrátíme částečně naplněný list
                            list.Clear();
                            return list;
                        }

                        list.Add(currObject);
                    }
                
            }

            return list;
        }
    }
    

Při přetypovávání stačí stále jen zadat prototyp anonymního datové typu.

 

//Anonymní typ z jiné assembly!
            var result2 = TestAT.GetResult().CastToList(new {FirstLetter = default(char), 
                                                        Index =default(int),
                                                        Original = default(string),
                                                        InnerAT = new { X = default(char), B = new { A = default(int) } }
            })
                                                       ;
            foreach (var res in result2)
            {
                Console.WriteLine(res.FirstLetter);
                Console.WriteLine(res.Original);
            }


            Console.WriteLine(TestAT.
                                    GetResult().
                                    CastToList(new
                                    {
                                        FirstLetter = default(char),
                                        Index = default(int),
                                        Original = default(string),
                                        InnerAT = new { X = default(char), B = new { A =default(int)} }
                                    }
                                    ).
                                    Where(car => car.FirstLetter == 'T')
                                     .FirstOrDefault()
                                     .ToString());
            Console.ReadLine();

Friday, May 09, 2008 10:09:26 AM (Central Europe Daylight Time, UTC+02:00)  #     
Comments [0]  .NET Framework | ASP.NET | Compact .Net Framework | Windows Forms


 Thursday, May 08, 2008
LINQ - anonymní typ deklarovaný v jedné assembly dostupný v metodách další assembly?
.Net Framework

Anonymní datové typy v LINQu nelze použít jako návratový typ z metody a jediný způsob, jak anonymní typ z metody předat, je použít jako návratovou hodnotu typ object, protože v .Net Frameworku - jak je všeobecně známo - všechny třídy přímo či nepřímo dědí z třídy Object. Navíc platí, že anonymní typ je kompilátorem vždy deklarován jako internal a jeho použití je tak striktně omezeno na jednu assembly.

Jde o rozumné omezení a anonymní datové typy bychom neměli zneužívat k nesmyslům typu "hezká syntaxe pro generování objektů Dictionary", které si našly cestu i do připravovaného (a už dnes "přehypovaného") MVC frameworku pro ASP.NET.

V různých diskuzích se ale stále dokola objevuje dotaz, jak anonymní typ z metody vráti. A každé omezení se dá samozřejmě obejít - když nefunguje ani bodový systém na silnicích, proč nenajít hrubý trik ve stylu "osoby blízké" i pro erozi různých omezení u anonymního datového typu. :) Znovu alibisticky varuji všechny před zařazením následujících nehezkých triků do svého arzenálu běžných postupů při vývoji, protože všechny postupy spoléhají na chování kompilátoru C#, které není garantováno a které se může v další verzi nebo i jen při vydání service packu .Net Frameworku bez varování změnit.

Pro vrácení anonymního datového typu z metody použijeme hezký hack od Tomáše, který se ujal pod názvem "Cast By Example". Zjednodušeně řečeno - sice nemůžeme používat při přetypovávání názvy anonymních datových typů (tříd), protože anonymní datové typy jsou generovány až při kompilaci, ale můžeme kompilátoru dát při přetypování "vzor", jaký anonymní datový typ nám bude vyhovovat. Podrobnosti si můžete najít v odkazovaném článku Tomáše Petříčka  = zde jen připomenu, že technika využívá současného chování kompilátoru, který pro různé deklarace anonymních datových typů se stejnými vlastnostmi generuje v jedné assembly vždy právě jednu třídu.

Napsal jsem jednoduše použitelné extenze, které vám dovolí nejen přetypovat jednu instanci "object" na anonymní datový typ, ale můžete přetypovat množiny záznamů na (anonymně ;-)) typovou kolekci List<NějakýAnonymniTyp>, a dokonce je možné jednoduše použít anonymní datové typy z jiné assembly.

 

/// <summary>
    /// Rozšíření pro LINQ
    /// </summary>
    static class RSLinqExtensions
    {

        /// <summary>
        /// Metoda přetypuje objekt na anonymní typ, jehož struktura byla předána v parametru <paramref name="prototype"/>
        /// </summary>
        /// <typeparam name="T">Kompilátorem odvozený anonymní typ</typeparam>
        /// <param name="prototype">Prototyp se strukturou anonymního typu</param>
        /// <returns>Instanci anonymního typu, nebo null, jestliže konverzi nelze provést</returns>
        /// <remarks>Metoda se pokusí převést data z různých assembly</remarks>
        public static T ToAnonymousType<T>(this object obj, T prototype)
                                        where T: class
        {
            
            
            T atiObj = obj as T;
            
            if (atiObj == null)
            {

                ConstructorInfo constructorInfo = typeof(T).GetConstructors()[0];
                
                if (constructorInfo == null)
                {
                    return null;
                }

                ParameterInfo[] paramInfos = constructorInfo.GetParameters();                
                PropertyInfo[] origProperties = obj.GetType().GetProperties();
                

                if (paramInfos.Count() != origProperties.Count())
                {
                    return null;
                }

                object[] paramArgs = new object[paramInfos.Count()];


                for (int i = 0; i < paramArgs.Length; i++)
                {
                    PropertyInfo origProperty = origProperties.Where(prop => prop.Name == paramInfos[i].Name).FirstOrDefault();
                    
                    if (origProperty == null)
                    {
                        return null;
                    }
                                        
                    
                    paramArgs[i] = origProperty.GetValue(obj, null);                    
                }
                
                atiObj = constructorInfo.Invoke(paramArgs) as T;
            }
            
            return (atiObj);
        }
    
        /// <summary>
        /// Metoda vrátí
        /// </summary>
        /// <typeparam name="T">Kompilátorem odvozený anonymní typ</typeparam>
        /// <param name="prototype">Prototyp se strukturou anonymního typu</param>
        /// <returns>List instancí anonymního typu, nebo null, jestliže konverzi nelze provést</returns>
        /// <remarks>Metoda se pokusí převést data z různých assembly</remarks>
        public static List<T> CastToList<T>(this object obj, T prototype)
                                 where T : class
        {
            List<T> list = new List<T>();
            IEnumerable<T> enumerable = obj as IEnumerable<T>;

            if (enumerable != null)
            {
                list.AddRange(enumerable);
            }
            else
            {
                    IEnumerable enumObjects = obj as IEnumerable;
                    if (enumObjects == null)
                    {
                        return null;
                    }
                    
                foreach (object enumObject in enumObjects)
                    {
                        T currObject = ToAnonymousType(enumObject, prototype);
                        if (currObject == null)
                        {
                            //K čistění listu by neměl být důvod, ale garantujeme, že nevrátíme částečně naplněný list
                            list.Clear();
                            return list;
                        }

                        list.Add(currObject);
                    }
                
            }

            return list;
        }
    }

Komentáře u metod by měly dostatečně popisovat funkci extenzí. Metoda ToAnonymousType předpokládá, že chcete přetypovat na instanci anonymního typu (např. při použití metody Single v LINQu), metoda CastToList pracuje s množinou (IEnumerable<T>) instancí anonymního datového typu. Většina kódu v obou metodách ošetřuje situaci, kdy pracujete s anonymním datovým typem z jiné (referencované) assembly, jehož data je potřeba přenést do instance anonymního datového typu v aktuální assembly.

Použití extenzí - nejprve u anonymního datového typu deklarovaného v assembly, ve které je také náš LINQ dotaz.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using System.Collections;
using LINQTEST;

class Program
    {

        //Anonymní typ deklarovaný v této (exe) assembly
        private static object GetLetters()
        {
           string[] names = {"Rene", "Petra", "Kamilka"};

           var test = from name in names
                      select new {FirstLetter = name[0], Index=1};
           return test;
        }


        static void Main(string[] args)
        {
            var result = GetLetters().CastToList(new {FirstLetter = default(char),
                                                      Index =default(int)}
                                                 );
            foreach (var res in result)
            {
                Console.WriteLine(res.FirstLetter);
            }

}

Metodě CastToList jsme predali "vzor" anonymího datového typu (new {FirstLetter = default(char), Index =default(int)}) a hodnoty vlastností jsme u prototypu inicializovali s využitím klíčového slova default. V metodě Main v cyklu foreach je funkční intellisense a můžeme pracovat zcela typově s proměnnou res. Jenom zdůrazním, že nyní žádná reflexe nebyla použita! Metoda CastToList s využitím automatické typové inference kompilátoru pouze zkopírovala prvky v IEnumerable<T> do našeho typového generického Listu.

if (enumerable != null)
            {
                list.AddRange(enumerable);
            }

Reflexe je využita při konverzi anonymního typu deklarovaného v jiné assembly. Předpokládejme, že v jiné assembly nazvané např. LINQTest máme další metodu vracející množinu dat skrytou opět za obecným rozhraním "služebníka zcela neužitečného" neboli třídy object.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LINQTEST
{
    public class TestAT
    {
        public static object GetResult()
        {
            string[] rows = { "Toyota", "Lexus", "Audi" };

            var test = from row in rows
                       select new { FirstLetter = row[0],
                                    Index=110,
                                    Original = row
                                  };
            
            return test;

        }
    }
}

Zkompilovanou assembly LINQTest zareferencujeme v našem projektu. Kód pro práci s anonymní datovým typem v jiné assembly se z pohledu uživatele LINQ extenze nijak nezměnil od předchozího příkladu.

 

class Program
    {


        static void Main(string[] args)
        {
            //Anonymní typ z jiné assembly!
            var result2 = TestAT.GetResult().CastToList(new {FirstLetter =  default(char), 
                                                        Index =default(int),
                                                        Original = default(string)}
                                                       );
            foreach (var res in result2)
            {
                Console.WriteLine(res.FirstLetter);
                Console.WriteLine(res.Original);
            }


            Console.WriteLine(TestAT.
                                    GetResult().
                                    CastToList(new
                                    {
                                        FirstLetter = default(char),
                                        Index = default(int),
                                        Original = default(string)
                                    }).
                                    Where(car => car.FirstLetter == 'T')
                                     .FirstOrDefault()
                                     .ToString());
            Console.ReadLine();
        }
    }

Jak si můžete všimnout, po cyklu foreach si požádám o data z jiné assembly znovu a poté nad vrácenou typovou kolekci vytvořím další projekci. A ani mě nemusí zajímat, že se mi pod rukama zcela změnil typ používaných objektů. :-)

Docela zábavná záležitost ne? ;-)

LINQ II - přetypovávání i vnořených anonymních datových typů z jiné assembly


Thursday, May 08, 2008 4:00:43 PM (Central Europe Daylight Time, UTC+02:00)  #     
Comments [0]  .NET Framework | ASP.NET | Compact .Net Framework | Windows Forms


 Monday, May 05, 2008
Pozvánka na přednášku Pasti, strasti a propasti nativního/řízeného (managed) vývoje pro zařízení s operačním systémem Windows Mobile

Pozvánka na přednášku  Pasti, strasti a propasti nativního/řízeného (managed) vývoje pro zařízení s operačním systémem Windows Mobile

Ve čtvrtek 15.5.2008 přednáším na WUG v Hradci Králové o vývoji aplikací pro Windows Mobile. Jestliže vás téma zajímá a máte cestu okolo, rád vás uvidím. ;)

Místo konání: Střední škola aplikované kybernetiky, Hradecká 1151, Hradec Králové (www.ssakhk.cz)
Datum a čas akce: 15.5.2008 17:00:00
Stránka WUG s podrobnostmi o akci: http://www.wug.cz/Aktuality/tabid/36/ctl/Detail/mid/492/ItemId/124/language/cs-CZ/Default.aspx

 


Monday, May 05, 2008 6:00:08 PM (Central Europe Daylight Time, UTC+02:00)  #     
Comments [0]  Compact .Net Framework | Mobilitky | Net Monitor | Wifi Profiles Windows Mobile


 Thursday, April 24, 2008
Vlastní reakce na podrženi stylu uživatelem (zobrazení kontextového menu)

Další z triků pro Compact .Net Framework, na který se lidé často ptají. Chcete sami zareagovat na podržení stylu místo výchozího zobrazení kontextového menu, což zajišťuje výchozí obsluha události přímo v CNF? Přes P/Invoke je to možné.

private const uint GN_CONTEXTMENU = 1000; 
private const uint SHRG_RETURNCMD = 0x00000001; 


[StructLayout(LayoutKind.Sequential)] 
public class SHINFO 
{ 
       public uint cbSize = 0; 
       public IntPtr hwndClient = IntPtr.Zero; 
       public int x = 0; 
       public int y = 0; 
       public uint dwFlags = 0; 
} 
[DllImport("aygshell", SetLastError = true)] 
private static extern uint SHRecognizeGesture(SHINFO shrg); 


//Obsluha udalosti MouseDown formulare/ovladaciho prvku
private void mouseDown(object sender, MouseEventArgs e) 
{ 

   SHINFO shinfo = new SHINFO(); 
   shinfo.cbSize = (uint)(Marshal.SizeOf(shinfo)); 
   shinfo.hwndClient = this.Handle; //handle formulare/ovladaciho prvku
   shinfo.x = e.X; 
   shinfo.y = e.Y; 
   shinfo.dwFlags = SHRG_RETURNCMD; 
   if (SHRecognizeGesture(shinfo) == GN_CONTEXTMENU) 
   {
     //nase reakce 
  } 
}

V nativním kódu můžeme vlastním zpracováním gesta GN_CONTEXTMENU odstranit nepříjemnou a snad od počátku přítomnou chybu v MFC, která způsobí, že se animace ("tečky opisující kruh") zobrazí 2x. Ukázka z Today pluginu (čisté API, ne MFC).

 

 

SHRGINFO shrg;
HMENU hMenu;

shrg.cbSize = sizeof(shrg);
shrg.hwndClient = m_hWnd;
shrg.ptDown.x = point.x;
shrg.ptDown.y = point.y;
shrg.dwFlags = SHRG_RETURNCMD ;

POINT screenPoint = point;
ClientToScreen(m_hWnd, &screenPoint);


if (!tabControl.HasFocus())
{
:PostMessage(GetParent(), TODAYM_TOOKSELECTION, (WPARAM)m_hWnd, 0);

}

if (SHRecognizeGesture(&shrg) == GN_CONTEXTMENU) 
{
//Nas kod
}

Thursday, April 24, 2008 1:11:30 PM (Central Europe Daylight Time, UTC+02:00)  #     
Comments [0]  Compact .Net Framework | Mobilitky


 Sunday, October 29, 2006
Kontrola duplicitního spuštění aplikace v Compact .Net Frameworku - Windows CE

I když samotný Compact .Net Framework v souladu s doporučeními Microsoftu pro vývoj Windows Mobile aplikací zajišťuje, že je vždy spuštěna nanejvýš jedna instance aplikace, v Compact .Net Frameworku spuštěném na "čistých" Windows CE již zmíněné pravidlo neplatí a kontrolu na opakované spuštění aplikace musíme doplnit sami.

Tento kód funguje pro Compact .Net Framework od verze 1.x a byl otestován na Windows CE 4.2 a vyšších.

private const int ERROR_ALREADY_EXISTS = 183;

[DllImport("CoreDll.dll")]
private static extern int GetLastError(); 

[DllImport("CoreDll.dll", EntryPoint="CreateMutexW")]

private static extern int CreateMutex( IntPtr
lpMutexAttributes, bool InitialOwner, string MutexName );

public static bool AppAlreadyStarted() 
{ 
string myID =
System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;

if( CreateMutex(IntPtr.Zero, true, myID) != 0 )
{
    return (GetLastError() == ERROR_ALREADY_EXISTS);
} 

return false; 
}

Můžeme také najít formulář již spuštěné aplikace podle titulku, přenést jej do popředí a duplicitní instanci aplikace ukončit.

namespace DeviceApplication1
{
static class Program
{
[DllImport("coredll", EntryPoint="FindWindow")] 
private static extern IntPtr FindWindow( 
string lpClassName, 
string lpWindowName); 


[DllImport("coredll", EntryPoint="SetForegroundWindow")] 
private static extern bool SetForegroundWindow(IntPtr hWnd); 

[MTAThread]
static void Main()
{


IntPtr mainWindowHwnd = FindWindow(null, "DeviceMain");


if (mainWindowHwnd.Equals(IntPtr.Zero)) 
{ 
   Application.Run(new Form1()); 
} 
else 
{
   SetForegroundWindow(mainWindowHwnd); 
   Application.Exit(); 
} 

}

}

Sunday, October 29, 2006 8:52:11 PM (Central Europe Standard Time, UTC+01:00)  #     
Comments [0]  Compact .Net Framework


 Monday, May 01, 2006
Odstranění problému(ů) s instalací Compact .NET Frameworku 2.0 na Windows Mobile 5.0

Již několikrát jsem se setkal s tím, že na zařízení s Windows Mobile 5.0 byl problém nainstalovat Compact .Net Framework. Pár lidí si mi teď v poslední době v mailu nebo na ICQ také stěžovalo, že jim CNF nejde nainstalovat a že při pátrání po příčinách potíží dostanou nanejvýš jen hlášku, že došlo k problému a že by bylo dobré resetovat zařízení a zkusit instalaci znovu. ;-) Tomu říkám skvělé design guidelines v praxi - "nikdy neobtěžujte uživatele zbytečnými technickými podrobnostmi". :-D

Odstranění problému není složité - z důvodu známému asi jen vývojářskému týmu CNF v Microsoftu a mystérióznímu pro běžného smrtelníka ActiveSync při instalaci CNF z počítače přes Application Manager zkopíruje do zařízení soubor nazvaný NETCFv2.wce5.armv4i.cab místo správného NETCFv2.wm.armv4i.cab. NETCFv2.wm.armv4i.cab patří na všechna současná zařízení používající procesor s ARM V4 kompatibilní instrukční sadou.

Takže selže-li vám instalace, najděte na počítači soubor NETCFv2.wm.armv4i.cab, a zkopírujte jej "ručně" (v Exploreru nebo třeba v v Total Commanderu s pluginem pro CE zařízení)  do PDA. V PDA pak jen cab soubor spusťte a VŽDY zvolte instalaci CNF do zařízení - instalace na kartu (SD, CF) je sice možná, ale při prvním použití CNF dojde stejně k nakopírování všech knihoven do storage v zařízení.

Pokud máte stále problémy s instalací:

  1. Resetujte (soft-reset) zařízení a zkuste nainstalovat CNF znovu. Teď už má reset smysl. ;-)
  2. Nezabere-li ani to, vypněte po dobu instalace všechny Today pluginy, restartujte (soft reset) a znovu instalujte.

Přinejhorším bod 2 zabere podle mých zkušeností vždy - ještě se mi nestalo, že bych na nějaké PDA s WM 5 CNF 2 nenainstaloval ;-)

 


Monday, May 01, 2006 10:50:26 PM (Central Europe Daylight Time, UTC+02:00)  #     
Comments [0]  Compact .Net Framework | Mobilitky


 Sunday, April 09, 2006
Bázová třída pro typové kolekce v .NET Frameworku 2.0

V souvislosti s uvedením generiky v .NET Frameworku se v různých článcích dočtete, jak generika usnadní vytváření a použití typových kolekcí. To je sice pravda, ale v článku se kromě zjednodušených příkladů a frikulínských hlášek o dokonalosti .NETu 2.0 z úst (respektiva pera) excitovaných jedinců po právě dokonaném intimním styku se softwarovou emanací božstva Microsoftu :-D málokdy dovíte, jak by taková typová kolekce měla vypadat v běžné aplikaci.

Proč nevyhovuje obyčejné použití generického typu List? (deklarace typové kolekce objednávek ve tvaru List<Order> orderCollection = new List<Order>()).

  • Jednou z dobrých praktik u generik je co nejvíce před uživateli (dalšími vývojáři) skrývat informaci, že pracují s generickým typem. Ač mně syntaxe pro práci s generickými typy připadá intuitivní, nemusí si to myslet všichni, a mnoho vývojářů stále asi raději používá důvěrně známý kód OrderCollection orderCollection = new OrderCollection() místo výše zmíněného kódu List<Order> orderCollection = new List<Order>()).  Tento požadavek by ale List<T> splnil  - typová kolekce může být potomkem List<T>.
  • Ve třídě List nejsou metody Add a Remove virtuální. To znamená, že nemůžete po přidání nebo odebrání položky z/do kolekce vyvolávat vlastní události. A to je problém, protože po přidání/odebrání položek z kolekce můžeme chtít nastavit/zrušit rodiče nebo přepočítát sumární hodnoty za položky v kolekci apod.

Bázová třída pro všechny kolekce, kterou používám, je otevřeným generickým typem a jejím předkem je třída Collection<T> z jmenného prostoru System.Collections.ObjectModel. Třída Collection<T> nabízí virtuální chráněné metody InsertItem a RemoveItem, ve kterých můžete vyvolávat potřebné události. Jestliže používate návrhový vzor Layer SuperType a máte tedy jednu bázovou třídu pro všechny business objekty (BusinessObjectBase), je vhodné, aby bázová třída pro kolekce kladla na generický typ T omezení, že musí být potomkem třídy BusinessObjectBase. Omezení slouží k tomu, abyste ve svých kolekcích mohli intuitivně používat všechny atributy a metody deklarované na úrovni společného předka BusinessObjectBase.

Kód kolekce:

   public class BusinessCollectionBase<T> : Collection<T> 
                                        where T : BusinessObjectBase
    {
        #region Events
        public event EventHandler<CollectionChangeEventArgs> ItemAdded;
        public event EventHandler<CollectionChangeEventArgs> ItemRemoved;
        #endregion Events
        #region Protected methods
        /// <summary>
        /// Přidání položky do kolekce
        /// </summary>
        /// <param name="index">Index položky</param>
        /// <param name="item">Vkládaná položka</param>
        protected override void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
            OnItemAdded(new CollectionChangeEventArgs(item));
        }

        /// <summary>
        /// Přidání položky do kolekce
        /// </summary>
        /// <param name="index">Index položky</param>