Skip to content

Sumários

Miguel Gamboa edited this page Jun 21, 2021 · 43 revisions

Aulas:

Reference: Essential .NET, Volume 1: The Common Language Runtime



  • Virtual Execution Environment
  • Informally virtual machine or runtime
  • C# is NOT a goal
  • Historic evolution since Java to .Net nowadays
  • Examples of languages targeting JVM: Java, kotlin, Scala, Clojure
  • Examples of languages targeting .Net: C#, F#, VB,
  • C# essentials is equal to Java, E.g. class A { static void foo() { }}

  • Component - Reusable software unit, with:
    • IR - code in intermediate representation (e.g. bytecodes, IL, other)
    • Metadata - auto description
    • Ready to use => does not require static compilation.
    • API => conforms to a public interface.
    • Indivisible => 1 module (file)
  • Software development by Components:
    • Reduce Complexity;
    • Promote Reuse.
  • Developer roles:
    • Provider - provides a component with a well-known API (e.g. Point)
    • Client - uses the component from a provider to build an Application, or another component (e.g. App).
  • Unmanaged <versus> Managed
  • Static <versus> Dynamic link

  • Building unmanaged components with static link:
    • NOT a unit, but two parts instead: header + obj
    • Need of a header file that describes the component content
    • Different builds for different architectures
    • Structural modifications (new header) => compilation + link
    • Behavioral modifications => link
  • Demo with an unmanaged App using a Point.
  • VS tools for C unmanaged:
    • cl /c -- compile and produces an obj
    • link comp1.obj comp2.lib etc -- static link
  • Demo with a managed App using a Point.
    • Jitter (just-in-time compiler) - compiles IR to native code (e.g. x86, amd64, ppc) at runtime
    • Dynamic link - Point.class on App compilation + link at runtime.
    • Lazy Load - Point.class is loaded only when needed

  • Solving TPC01
  • Comparing Java components with Dotnet componentes: .class versus .dll
    • .class with a single type
    • .dll with zero or more types
  • Using Java disassemble tool: javap -c ...
  • Using Dotnet disassemble tool: ildasm
  • Highlight metadata and intermediate representation (bytecode or IL)
  • Implicit definitions:
    • default constructor: <init> (Java) and .ctor (dotnet)
    • call base constructir: invokespecial Object."<init>" and call Object::.ctor
  • dependences
    • Java classpath
    • dotnet .csproj and reference to library

  • Dotnet Type System -- CTS (Common Type System) ECMA 335
  • Reflection -- System.Reflection in dotnet and java.lang.reflect in Java.
  • Reflection object oriented API for metadata
  • Dotnet Reflection API (System.Reflection Namespace): Type, FieldInfo, MethodInfo, ConstructorInfo, PropertyInfo, EventInfo and abstract class MemberInfo, which is the base type for aforementioned types.
  • Java Reflection API (java.lang.reflect): Class, Member, Method e Field.

  • Type is representing a type in Dotnet, whereas Class is representing a type in Java.
  • Assembly is representing a component in Dotnet (.dll)
  • Assembly::LoadFrom(String path) -- Static method returns an Assembly instance for the component in given path
  • Assembly::GetTypes() -- returns an array of Type instances for all types in that Assembly instance (i.e. component).

  • Homework:
    • Load(name) versus LoadFrom(path)
    • fully qualified assembly name e.g. "SampleAssembly, Version=1.0.2004.0, Culture=neutral, PublicKeyToken=8744b20f8da049e3"
    • GetTypes(), GetMethods(), MemberInfo.Name
  • Dotnet solution and dependencies:
dotnet new sln
dotnet new classlib -o Logger
dotnet sln add Logger\Logger.csproj
dotnet new console -o App
dotnet sln add App\App.csproj
cd App
dotnet add reference ..\Logger\Logger.csproj
  • Distinguishing the role of App ---> Logger.
  • Version 1: LogFields and LogMethods:
    • FieldInfo::GetValue(object target);
    • MethodInfo::Invoke(object, object[])
    • MethodInfo::GetParameters() and ParameterInfo
  • Version 2: LogMembers

  • Java runtime type information – Class;
  • Dotnet runtime type information – Type;
  • GetType() returns the same object (same identity) for objects of the same class.
  • VM maintains a single info Type instance for each loaded type.
  • getClass() <=> GetType()
  • p is Point <=> p instanceof Point
  • C# operator as returns null on unsuccess;
  • Cast throws exception on unsuccess;

  • Unit tests franeworks, E.g. JUnit, TestNG, NUnit, XUnit, and others
  • dotnet new xunit
  • How Unit tests franeworks distinguish test methods from others?
    • By name prefix;
    • By annotation, e.g. @Test, [Test], Fact
  • AAA idiom: Arrange, Act and Assert
  • How to test a void method like Info()?
  • Replace dependency Log ----> Console with a new IPrinter.
  • Log uses by default an instance of ConsolePrinter, whereas unit tests use a BufferPrinter to collect the output.
  • Augment Logger API to include a ToLog annotation that let clients select which members should be logged.

  • .net typeof(...) <=> Java .class
  • GetType() vs typeof -- run time <versus> compile time
  • Custom attributes usage:
    • let developers annotate code, e.g. [ToLog] string name; ...
    • stored in metadata
  • Defining new custom attribute: a class that extends Attribute
  • Inspecting custom attributes at runtime through the Reflection API:
    • Attribute::IsDefined(MemberInfo, Type) --> boolean
    • Attribute::GetCustomAttribute(MemberInfo, Type) --> object
    • MemberInfo::GetCustomAttributes(MemberInfo, Type) ---> object[]
  • Custom attributes with parameters. E.g:
    • class ToLogAttribute : Attribute { public ToLogAttribute(String label) { ...
    • Usage: [ToLog("Blue and White")] string name; ...
  • AttributeUsageAttribute => constraints on target and AllowMultiple, E.g:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Method, AllowMultiple=true)]
public class ToLogAttribute : Attribute {
    public ToLogAttribute(String label)
    {
      ...
  • Logger supporting Arrays
  • Arrays are compatible with IEnumerable
  • Compatibility check alternatives:
    1. typeof(IEnumerable).IsAssignableFrom(o.GetType())
    2. o is IEnumerable ? Inspect((IEnumerable) o) : ...
    3. IEnumerable seq = o as IEnumerable; ... seq != null ? Inspect(seq) : ...
  • Details about IL operators isinst and castclass
  • Reflection API IsSubclassOf and IsAssignableFrom
  • Overhead of ShouldLog with 4 verifications and an isinst conversion:
if(!Attribute.IsDefined(m,typeof(ToLogAttribute))) return false;
if(m.MemberType == MemberTypes.Field) return true;
return m.MemberType == MemberTypes.Method  && (as MethodInfo).GetParameters().Length == 0;
  • Refactor Logger to minimize the use of Reflection checks:
    • Keep Dictionary<Type, List<MemberInfo>>();

  • Refactor Logger to avoid if/else validation on LogMembers() execution.
  • Drop Dictionary<Type, IEnumerable<MemberInfo>>()
  • Use Dictionary<Type, IEnumerable<IGetter>>()
  • interface IGetter { string GetName(); object GetValue(object target);}
  • Distinct implementations: GetterField, GetterMethod, ...
  • Must use the same approach on FireMapper Workout with an interface specifying the way of dealing with properties

  • Stack based execution model.
  • Arguments and local variables tables.
  • Operators st... and ld....
  • call operator.
  • Compile with optimizations: dotnet build -c release

  • System.Reflection.Emit:
    • AssemblyBuilder, ModuleBuilder, TypeBuilder, MethodBuidler and ILGenerator
  • Following the example of defining a dynamic assembly with one module AssemblyBuilder

  • Goal: Avoid Reflection on IGetter implementation:
    • Both GetName() and GetValue() should not use the Reflection API.
  • Define a new abstract class GetterBase with the implementation of GetName() from a literal:
    • The GetValue() remains abstract, for now.
  • Follow a use case of a derived class of GetterBase:
    • e.g. hard-coded StudentNameGetter to access the field name
    • e.g. hard-coded StudentNrGetter to access the field nr
  • Notice the difference between conversions:
    • string ---> object: implicit
    • int ---> object: boxing corresponding to box Int32
  • CreateIGetterFor(Type domain, String field) that generates a dynamic implementation of BaseGetter for a given field in domain type.
  • Development approach:
    1. Use Reflection to emit IL and Do NOT emit IL that use Reflection
    2. First make a dummy hard-coded implementation that exemplifies the dynamically generated class. E.g. StudentNameGetter, StudentNumberGetter. Notice: these classes do not use Reflection
    3. Compile those classes from 2. and check the resulting IL.
    4. Develop a dynamic builder that produces similar implementations to the IL from 3, i.e. CreateIGetterFor
    5. Save the resulting dll and check its correctness through PEVerify.exe

  • Refactor CreateIGetterFor to CreateIGetterFor(Type domain, MemberInfo member);
  • AbstractLog with two implementations: Log and LogDynamic;
  • AbstractLog implements LogMembers() and has abstract method GetMembers();
  • Categories of Types: Value Types and Reference Types;
  • Values versus Objects;
  • Object layout;
  • Object header: sync# and htype (Type Handle)
  • Runtime type information -- COREINFO_CLASS_STRUCT
  • Value types extend from System.ValueType;
  • User-defined value types cannot be further extended.

  • Reference Types are instantiated in Csharp with new => IL newobj
  • Value Types are NOT instantiated, but initialized instead:
    • Invoking the parameterless constructor of a value type results in a initobj;
    • initobj initialize all fields with default values (0 or null).
  • Conversion Ref Type <=> Value Type -- boxing and unboxing.
  • primitive types can be either Value Types or Reference Types, e.g. int, string, etc.
  • !!! .Net: int <=> System.Int32 -- there is NO conversion
  • !!! In Java: int != java.lang.Integer -- there is boxing or unboxing.

  • Reference Types are instantiated in C# through new => IL newobj
    1. Allocates storage on Heap
    2. Initializes space with zeros + Header (htype pointing to RTTI)
    3. Calls the constructor
    • Returns the reference of newbie instance
  • Value tyoes are NOT instantiated but only initialized:
    • Calling the parameterless constructor in C# results in initobj (initializes fields with 0/null)
  • Details about IL ldloca
  • Example with ldloca and initobj
  • e.g. Given Point as struct then calling new Point(8, 9) is NOT initobj but call Point::ctor(...) instead.
  • Structs cannot contain explicit parameterless constructors!

  • Remember instance methods and virtual methods (virtual in C#).
  • Overriding methods require override keyword in C#.
  • callvirt not only for virtual methods, but also to call any instance methods on references.
  • callvirt performs an additional verification to check target (i.e. this) is not null.
  • Value types does not require that verification because they are non-null.
  • Instance methods on Value Types are called with call because they are non-virtual and their target cannot be null.

  • Performance Evaluation and Benchmarking
  • Benchmarking checks Performance != Unit tests checks correction
  • E.g. Compare Log (Reflection) with LogDynamic
  • Performance evaluation requirements:
    • Focus on CPU bound operations
    • Eliminate overheads, such as, initializing instances, setup, bootstrap, etc.
    • Avoid IO operations, such as standard output, networking, etc...
    • Reduce the VM side-effects => execute many iterations
  • Results approaches: Average, Standard deviation or the best result.
  • Results units: Durations in milliseconds, seconds, whatever... or Throughput - number of operations in a time slot, e.g. ops/ms, ops/sec
  • NBench - implementing a performance measure tool.

  • Given interface IFormatter { object Format(object val);} add support to IFormatter feature at Log (reflect), such as:
class LogFormatterFirstName : IFormatter {
  public object Format(object val) => ((string) val).Split(' ')[0];
}
public class Student {
  [ToLog] public readonly int nr;
  [ToLog(typeof(LogFormatterFirstName))] public readonly string name;
  ...
}
...
class LogFormatterTruncate : IFormatter {
  private readonly int decimals;
  public LogFormatterTruncate(int decimals) { this.decimals = decimals; }
  public object Format(object val) => return Math.Round((double) val, decimals);
}
public class Point{
  [ToLog] public readonly int x;
  [ToLog] public readonly int y;
  [ToLog(typeof(LogFormatterTruncate), "1")] public double GetModule() {
      return System.Math.Sqrt(x*x + y*y);
  }
  ...
}
  • Do not change AbstractLog.
  • You may:
    • override the ShouldLog method.
    • Implement new classes.
    • change the ToLogAtribute
  • Step 1: Implement the formatter feature admitting that IFormatter class has a parameter less constructor.
    • e.g. [ToLog(typeof(LogFormatterFirstName)) or [ToLog(typeof(LogFormatterTruncate))]
  • Step 2: Enable the use of IFormatter through a constructor with parameters:
    • e.g. [ToLog(typeof(LogFormatterTruncate), "1")]

  • Sequences or Streams, e.g. [], IList, IEnumerable, ..
  • Implementing utility functions for sequences: convert and filter
  • Stream pipelines composition

  • Workout: Given an IEnumerable get a new sequence with the first name of all students beginning with a "D" and number greater than 47000.
  • Version 1: ConvertToStudents, ConvertToName, FilterNameStartsWith e FilterWithNumberGreaterThan.
  • Version 2: Remove the dependency of the domain with a single Convert and Filter:
    • IEnumerable Convert(IEnumerable src, Function mapper)
    • IEnumerable Filter(IEnumerable src, Predicate conv)
  • Interfaces Function and Predicate:
    • interface Predicate { bool Invoke(object item); }
    • interface Function { object Invoke(object item); }

  • Version 3: Use delegate rather than interface for Function and Predicate.
  • A delegate instance contains a method handle (i.e. method reference)
  • Delegate is a type safe wrapper for a method handle
  • That method can be invoked through the delegate E.g. for Function f then we may f.Invoke(n).
  • f.Invoke(n) <=> f(n)

  • Fields _methodPtr and _target of Delegate
  • Delegate property Method to get the MethodInfo corresponding to that method.
  • Delegate property Target to get the _target
  • The delegate constructor receives 2 arguments to initialize the 2 fields _methodPtr and _target.
  • new FunctionDelegate(AppQueries3.ToStudent) <=> AppQueries3.ToStudent
  • IL instructionldftn to get the method handle

  • Lambda expressions define anonymous methods.
  • Anonymous methods have a name generated by the compiler.
  • E.g. o => Student.Parse((string) o) ---> '<Main>b__3_0'(object)

  • Differences between Eager versus Lazy processing.
  • Details about IEnumerable and IEnumerator.
  • Version 5: Lazy implementation of Converter and Filter.
  • Horizontal versus Vertical processing.

  • Generics:
    • Generic classes - classes with type parameters
    • Generic methods – methods with type parameters ( != formal parameters)
  • Using a generic class or methos requires a type argument for every type parameter
  • Type inference of type arguments
  • Sequences with generic methods, e.g. Convert<T, R>() and Filter<T>()
  • System Delegates: Func<T,R> e Predicate<T>

Pretende-se desenvolver uma biblioteca para definição e validação de regras sobre propriedades de objectos. Uma regra é representada pela interface IValidation { bool Validate(object obj); } (exemplos de implementações desta interface: Above18, NotNull).

Uma instância de Validator<T> representa validadores para uma entidade de domínio de tipo T (e.g. Student, Artist, Track, etc), sobre a qual é possível acrescentar regras de validação para determinadas propriedades (identificada pelo seu nome), tal como apresentado no exemplo seguinte:

Student s = new Student(); s.Age = 20; s.Name = "Anacleto";
Validator<Student> validator = new Validator<Student>()
                .AddValidation("Age", new Above18())
                .AddValidation("Name", new NotNull());
validator.Validate(s);
class Student {
  public int Age {get; set; }
  public String Name{get; set; }
}

Requisitos:

  • Não use qualquer tipo de colecção como List, Dictionary ou outra.
  • Podem ser adicionados diferentes validações sobre a mesma propriedade.
  • Uma instância de Validator<T> deve ser imutável. Ou seja, validator1 e validator2 representam validadores diferentes no exemplo seguinte:
Validator<Student> validator1 = new Validator<Student>().AddValidation("Age", new Above18())
Validator<Student> validator2 = validator1.AddValidation("Name", new NotNull());
Student s = new Student(); s.Age = 20; s.Name = null;
validator1.Validate(s); // => succesful 
validator2.Validate(s); // => ValidationException

Exercício 1:

  1. [1] (warmup) Implemente a classe Above18, que retorna true se o valor recebido por parâmetro for superior a 18, ou false caso contrário.
  2. [2] Implemente a classe Validator<T> tendo em conta que o método Validate lança a exceção ValidationException, se falhar alguma das regras.

Exercício 2:

  1. [2] Sem alterar o código escrito anteriormente, acrescente o necessário para que seja suportada a adição de regras na forma de delegates do tipo Func<W, bool>, conforme demonstra o exemplo seguinte. Se a propriedade indicada não for do tipo W, então é lançada a exceção TypeMismatchException.
Validator<Student> validator = new Validator<Student>().AddValidation<String>("Name", UtilMethods.Max50Chars);
/* ... */ 
class UtilMethods { 
    public static bool Max50Chars(String s) {     /* ... */ }
}