Workshop 02 - C# .Net← Home → |
|
|
Some general aspectsAdd a new class named TstClass to the UPBDotNet projectImplement a public Id int property Test the newly created class using, for example, a command button: One could notice that: - The class could be instantiated even though no constructor was defined. A default constructor without parameters is automatically created. - It is possible to assign a TstClass instance to an object variable, as all classes are by default inheriting the object class (polymorphism) - object obj = 5 is also allowed. However, this is not polymorphism, but BOXING! - Even not defined in TstClass, one could call ToString() as it is a virtual method defined in the object base class. For changing the implicit behavior of ToString(), override it in the derived class. - var is not a type! It is used for type inferences. Thus, tstToString is a string variable. - As result of binary -> decimal conversions, in the below example, the doubleResult variable is very small, but not 0, as opposed to the decimalResult variable, which value is 0: Reflection (Runtime Type Information)For obtaining type information at runtime, use System.Reflection:As an example, it is often necessary to copy an instance (e.g. an entity) to a different class instance (e.g. a DTO = data transformation object). A reflection-based mechanism will save a lot of repetitive work. Important to notice: reflection is violating the encapsulation principle! Classes and methods could be "decorated" with attributes. For example, one could mark a function as obsolete: It is also possible to define custom attributes, by inheriting the Attribute base class: Custom attributes could be dynamically identified and treated in function of specific needs: Considering the high flexibility introduced by reflection, an important attention should be paid to security (e.g. malicious code injection)! Value vs Reference TypesAs opposed to a struct, which is value type, in .Net a class is reference type!Carefully analyze and test the following code: For comparing the data (not the addresses) of 2 instances, override the Equals virtual method: Together with Equals, it is highly recommended to override also GetHashCode accordingly. The hash code is useful, for example, when the type is used as key in a generic Dictionary. For creating a copy of the current instance, implement the "Clone" method: Strings are immutable reference type => string concatenation may result in performance issues. It is important to notice storing secrets in strings should be treated with caution (heap inspection vulnerability)! Observation! An immutable class could be implemented by providing a public read-only property (that could be assigned only via constructor) and by overloading the == operator. Thus, it will be impossible to assign a different value to an existing instance, but it will be possible to assign a different value to new instances. PointersPointers are variables that are storing addresses.Although not very frequently used in .Net, they are very useful in some particular scenarios. For example, in one image analysis software it was necessary to convert byte arrays into arrays of shorts. Thus, instead of copying elements one by one => O(n), one could use pointers => O(1): Deterministic vs Non-Deterministic destructionImplement a destructor:It is important to notice that objects are non-deterministically destroyed in .Net. For example, the destructor is not called when a variable goes out of scope. However, one can force garbage collection (unrecommended). When deterministic destruction is needed (e.g. closing a file) one could use the Dispose method provided by the IDisposable interface. It is very important to call Dispose even when exceptions are thrown => use try / catch / finally (the using construction could be employed for simplifying the below syntax): An interesting exerciseObservation! In .Net it is possible to use generics (classes in which one or more types are specified as parameters), e.g.: var list = new List<TstClass>();Carefully analyze the following code using breakpoints: Write a program for computing the following sum: S = 1! + 2! + ... + n! One could choose a structured solution: The structured solution is O(n2). A possible optimization could be observed: Would it be possible to provide a structured solution at O(n)? LINQLet's try implementing a "filter" => a method named "Where" that will provide only the elements with Id > 10. Of course, we can use yield return. We'll implement it in a static class named LinqTest:A code calling it will look something like this: In .Net it is possible to simplify the syntax, by implementing class extensions. Now, the Where method should not be prefixed any more by the class name when it is called. We can call it as if it was a method of the List class (even if it is not!): var result = list.Where(); I would also like to have more flexibility: pass the filter criterion as a parameter. For this, I will need a delegate (a function pointer), that accepts a TstClass as input parameter and returns a Boolean: Func is a generic delegate => a type safe function pointer. So, only addresses of functions with the expected signature will be accepted. Now, the criterion could be dynamically provided either by specifying a function address, or by using a lambda expression (anonymous functions that could be used inline): There's one more step left: make the Where function generic and change the parameter type from generic List to the, more general, IEnumerable interface: Thus, one could consider chains of such extension methods calls: The System.Linq namespace provides a set of such very useful generic extensions. Parallel programmingOne LINQ extension example is Sum(). It is important to notice LINQ supports parallelization: Parallel LINQ (PLINQ):Consider below an example of implementing the same Sum functionality using the Task class: For testing the previous function, one could use the following code: When writing parallel algorithms, an important attention should be granted to concurrent access. Async-AwaitAsync-Await is usually used for awaiting I/O operations => the thread is not interrupted, but is notified (event-driven mechanism) by OS! when the operation is completed.In the test example presented below, Task.Run will however result in creating a new thread. Test the below program using breakpoints. Notice that, in WinForm, async-await could be used for avoiding GUI freezing. Without await, the Console.WriteLine("Done!"); statement will be executed immediately (without waiting for the loop to finish). Async-await will break the method in 2 parts and will use the one containing the code which follows await as a "callback". References:Microsoft C# language documentationImplement a Dispose method |
|
← Home → |