5 užitečných tipů pro programování v C#

V poslední době jsem narazil na pár zajímavých vychytávek při tvorbě kódu. Rád bych se nyní s Vámi o tyto věci podělil. Pokud je znáte, tak dobře, pokud ne budu rád, když se inspirujete.

1. Generátor náhodných čísel

Chcete-li generovat velké množství náhodných čísel v jedné smyčce (například pro nastavení počátečních vah u neuronových sítí) může se vám stát, že generovaná čísla jsou stejná. Pokud řešíte tento problém lze použít následující konstrukci:

   1:  public class ClassWithRandomValue
   2:      {
   3:          private static readonly Random randomGenerator = new Random();
   4:          private static readonly object syncLock = new object();
   5:   
   6:          public double Value { get; set; }
   7:   
   8:          public void Initialize()
   9:          {
  10:              lock (syncLock)
  11:              {
  12:                  Value = randomGenerator.NextDouble();
  13:              }
  14:          }
  15:      }

2. Tracing

Zajímavá část .NET frameworku je bezesporu knihovna System.Diagnostic. Knihovna dovoluje sledovat části kódu a přispívat tak ke správnému chodu systému.

   1:  //Nastavení způsobu výpisu
   2:  Trace.Listeners.Add(new TextWriterTraceListener(System.Console.Out));
   3:   
   4:  // Výpis ohlášení
   5:  Trace.WriteLine("Chybové hlášení");

3. Sledování ukončování objektu

Poměrně dobrou metodou jak vylepšovat svůj kód a stabilitu celého systému je uchovávat veškeré dění v programu. Právě proto může být důležité zjistit kdy a kde se například při Unit testech ukončují objekty a uvolňuje se paměť. Následující ukázka ukazuje jak podmínit informování pouze na Debug nastavení.

   1:  class TestClass
   2:      {
   3:  #if DEBUG
   4:          ~TestClass()
   5:          {
   6:              System.Diagnostics.Debug.WriteLine("TestClass ukončena");
   7:          }
   8:  #endif
   9:      }

Při spuštění debug modu se pak ohlášení objeví v Output okně Visual Studia. Nebo jej můžete vypsat například do konzole:

   1:  TextWriterTraceListener writer = new TextWriterTraceListener(System.Console.Out);
   2:  Debug.Listeners.Add(writer);

4. Procesorový čas a spotřebovávaná paměť

   1:  Process thisProcess = Process.GetCurrentProcess();
   2:  TimeSpan processTimeBeforeLoop = thisProcess.TotalProcessorTime;
   3:   
   4:  long GC_MemoryStart = System.GC.GetTotalMemory(true);
   5:   
   6:  // Měřený úsek
   7:   
   8:  long GC_MemoryEnd = System.GC.GetTotalMemory(true);
   9:   
  10:  TimeSpan elapsedProcessor = thisProcess.TotalProcessorTime - processTimeBeforeLoop;

5. Generický převod datových typů

Následující konstrukci lze použít pokud potřebujete přetypovávat hodnoty genericky (například při vrácení hodnot z databáze):

   1:  public T ChangeType<T>(object item){
   2:  return (T)Convert.ChangeType(item, typeof(T));
   3:  }
Removing comments from string

If you want to delete a comment from a string or text file (such as C# code file), you can use regular expression.

The expression for it is very simple:

\/\*(.)*\*\/

There might be an error when you use the following approach:

string input = "/** Hello **/";
string output = Regex.Replace(@"\/\*(.)*\*\/", input, "");

You get this error:

ArgumentException: Nested quantifier *.

The solution is very simple again:

string input = "/** Hello **/";Regex regex = new Regex(@"\/\*(.)*\*\/");string output = regex.Replace(input, "");
Změna asociace v Entity Frameworku 4.0 při použití POCO

Entity Framework 4.0 přináší možnost pracovat s vlastními POCO třídami. Tento přístup je vhodný pro složitější vícevrstvé aplikace. Jestliže přístup do databáze přes Entity framework používají i jiné prostředí (C++, Java, Silverlight) nezbývala jiná možnost než napsat POCO třídy a pomocí vlastních algoritmů převést výstup z Entity Frameworku na tyto čisté třídy.

V nové verzi můžete využít přímého mapování bez nutnosti dědit z třídy frameworku, nebo implementovat rozhraní. Tento přístup však sebou přináší úskalí, kterým je nemožnost sledovat změny. Chcete-li sledovat změny můžete využít mechanizmu Self-tracking entities. Při použití tohoto mechanizmu musí každá entita implementovat rozhraní IObjectWithChangeTracker a INotifyPropertyChanged.

Jestliže se rozhodnete pro mapování pomocí čistých POCO tříd narazíte na problém, že metoda SaveChanges() neuloží změnu relace. Při řešení tohoto problému si je nutné nejdříve uvědomit, že framework považuje asociaci za relaci mezi dvěma objekty. Z toho je jasné, že tato relace může nabývat pouze stavů Unchanged, Added, Deleted. Nelze tak změnit stav relace na Modified. Základem řešení problému je tedy nutnost odpojit od entity původní entitu a připojit novou entitu.

Pro ukázku budou použity tabulky Product  a ProductCategory.

Article1_ef

Jak bylo napsáno výše pro úpravu asociace musí být odpojena stará vazba a připojena nová. Proto je vhodné vytvořit jednoduchou generickou strukturu.

Article1_change_tracker

Základní kód pro změnu asociace bude tedy vypadat následovně:

   1:  private static void UpdateRelationship<TItem, TChangeTrackerItem>(System.Data.Objects.ObjectContext context, TItem item, ChangeTracker<TChangeTrackerItem> changeTracker, string navigationProperty)
   2:  {
   3:              // Odebrání staré asociace
   4:              context.ObjectStateManager.ChangeRelationshipState(
   5:                  item,
   6:                  changeTracker.Original,
   7:                  navigationProperty,
   8:                  System.Data.EntityState.Deleted);
   9:   
  10:              // Přidání nové asociace
  11:              context.ObjectStateManager.ChangeRelationshipState(
  12:                  item,
  13:                  changeTracker.Current,
  14:                  navigationProperty,
  15:                  System.Data.EntityState.Added);
  16:  }

 

Jako nejobecnější a nejelegantnější řešení se nabízí vytvořit extension metodu pro ObjectSet<T>.

Metoda vypadá následovně:

   1:  public static void ChangeRelationship<TItem, TRelationship>(this System.Data.Objects.ObjectSet<TItem> objectSet, object item, string associationName)
   2:              where TRelationship : class
   3:              where TItem : class
   4:  {
   5:              ChangeRelationship<TItem, TRelationship>(objectSet.Context, item, objectSet.EntitySet.Name, associationName);
   6:  }

Metoda volaná na řádku číslo 5 vytvoří instanci ChangeTrackeru a pokud se instance liší dojde ke změně. Metoda vypadá následovně:

   1:  private static void ChangeRelationship<TItem, TRelationship>(System.Data.Objects.ObjectContext context, object item, string entitySetName, string associationName) where TRelationship : class
   2:  {
   3:              ChangeTracker<TRelationship> tracker = CreateChangeTracker<TRelationship>(context, entitySetName, associationName, item);
   4:   
   5:              // Jestliže došlo ke změně upraví se asociace.
   6:              if (!tracker.Current.Equals(tracker.Original))
   7:              {
   8:                  UpdateRelationship(context, item, tracker, associationName);
   9:              }
  10:  }
 

Nejdůležitější metodou je metoda CreateChangeTracker.

 

   1:  private static ChangeTracker<T> CreateChangeTracker<T>(System.Data.Objects.ObjectContext context, string entitySetName, string associationName, object item) where T : class
   2:  {
   3:              ChangeTracker<T> tracker = new ChangeTracker<T>();
   4:              context.AttachTo(entitySetName, item);
   5:   
   6:              var entryCurrent = context.ObjectStateManager.GetObjectStateEntry(item);
   7:   
   8:              // Nalezení vazby, která odpovídá dané relaci mezi dvěma objekty.
   9:              System.Data.Objects.DataClasses.EntityReference<T> endCurrent = (System.Data.Objects.DataClasses.EntityReference<T>)entryCurrent
  10:                  .RelationshipManager
  11:                  .GetAllRelatedEnds()
  12:                  .Where(e => e.TargetRoleName.Equals(associationName))
  13:                  .FirstOrDefault();
  14:   
  15:              var entry = context.ObjectStateManager.GetObjectStateEntry(endCurrent.Value);
  16:              tracker.Current = (T)endCurrent.Value;
  17:   
  18:              // Načtení originálního objektu z databáze a donačtení asociace.
  19:              object original = GetOriginal(context, entitySetName, item);
  20:              context.AttachTo(entitySetName, item);
  21:              context.LoadProperty(item, associationName);
  22:   
  23:              // Načtení asociace z originálního objektu.
  24:              var entryOriginal = context.ObjectStateManager.GetObjectStateEntry(item);
  25:              System.Data.Objects.DataClasses.EntityReference<T> endOriginal = (System.Data.Objects.DataClasses.EntityReference<T>)entryOriginal
  26:                  .RelationshipManager
  27:                  .GetAllRelatedEnds()
  28:                  .Where(e => e.TargetRoleName.Equals(associationName))
  29:                  .FirstOrDefault();
  30:   
  31:              tracker.Original = (T)endOriginal.Value;
  32:              return tracker;
  33:  }

Metoda zjistí z databáze stav původní a současné entity a do instance třídy ChangeTracker uloží asociace, které tyto třídy mají. Zjišťování původního stavu probíhá pomocí metody GetOriginal:

   1:  private static T GetOriginal<T>(System.Data.Objects.ObjectContext context, string entitySetName, T current) where T : class
   2:  {
   3:              // Připojení současného objektu ke kontextu.
   4:              context.AttachTo(entitySetName, current);
   5:              // Vyhledání stavu v kontextu
   6:              System.Data.Objects.ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(current);
   7:              // Nalezení podle klíče
   8:              System.Data.EntityKey key = entry.EntityKey;
   9:              T originalItem = (T)context.GetObjectByKey(key);
  10:              // Odpojení originálních dat od kontextu.
  11:              context.Detach(originalItem);
  12:              return originalItem;
  13:  }

 

Vytvořenou extension metodu stačí zavolat například v repozitory při ukládání upravené entity.

   1:  public Shared.Product Update(Shared.Product item)
   2:  {
   3:              using (ArticleShopEntities context = new ArticleShopEntities())
   4:              {
   5:                  // Připojení entity k aktuálnímu contextu.
   6:                  context.Product.Attach(item);
   7:                  
   8:                  // Pokus o změnu asociace. 
   9:                  // Jestliže jsou původní a nová asociace stejné nedochází k žádné změně.
  10:                  context.Product.ChangeRelationship<Product, ProductCategory>(item, "ProductCategory");
  11:   
  12:                  // Nastavení produktu na modifikovaný stav.
  13:                  context.ObjectStateManager.ChangeObjectState(item, System.Data.EntityState.Modified);
  14:                  
  15:                  // Uložení produktu.
  16:                  context.SaveChanges(System.Data.Objects.SaveOptions.DetectChangesBeforeSave | System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
  17:              }
  18:              return item;
  19:  }

Extension metoda ChangeRelationship může být použita pro změnu relace 1:N.

Není tedy nutné používat generované Proxy nebo Self-tracking entities.

V příštích několika dnech nahraji hotovou knihovnu zde pod příspěvek.

Vyvojar.cz na prodej!