Jump to content
  • C# (C Sharp) Lezione #3 - Programmazione Guida passo per passo


     Share

    Metadati & Reflection

     

    Annotazioni per assembly, tipi, membri e parametri

     - In Java @Blabla

     - Un pò tipo i modificatori public, private,..

     - Ma definite dall'utente

       . sono classi che estendono System.Attribute

       . per esempio, Serializable (System.SerializableAttribute) su una classe indica che i suoi oggetti sono serializzabili

     

    public class TapAttribute : Attribute { }
    [Tap] //classe annotata con Tap
    public class Program {
      [Tap]
      public static void Main(string[] args) {
      }
    }

     

    Dependency Injection

    Un esempio di classe per spedire email:

    public class EmailSender {
      public bool SendEmail(string to, string body) {
        if (to==null)
          throw new ArgumentNullException(nameof(to));
        if (body==null)
          throw new ArgumentNullException(nameof(body));
        //...send mail and return true
        return false;
      }
    }

    Questa classe e i suoi tipi ausiliari sono un esempio di componente (una volta "impacchettate" in una DLL)

     

    BulkEmailSender

    public class BulkEmailSender {
    	private readonly EmailSender _emailSender;
    	private readonly string _footer;
    	public BulkEmailSender(string footer) {
    		this._emailSender = new EmailSender();
    		this._footer = footer;
    }
    public void SendEmail(List<string> addresses, string body) {
    	if (addresses == null)
    		throw new ArgumentNullException(nameof(addresses));
    	if (body == null) throw new ArgumentNullException(nameof(body));
    	foreach (var a in addresses) {
    		if (!this._emailSender.SendEmail(a, body + this._footer))
    			throw new Exception("Cannot send email");
    		}	} 	}

     

    Esistono vari tipi di DI, consideriamo constructor injection (è quella da usare di default) che inietta tramite i costruttori

     - Altre sono la method, property, field,..

    L'idea è estremamente semplice: i costruttori richiedono gli oggetti che servono direttamente, invece di crearli

     

    in codice (senza factory)

    public BulkEmailSender(string footer) {
    	this._emailSender = new EmailSender();
    	this._footer = footer;
    }
    public BulkEmailSender(IEmailSender
    	emailSender, string footer) {
    	this._emailSender = emailSender;
    	this._footer = footer;
    }

     

    in codice (con factory)

    public BulkEmailSender(string footer) {
    	this._emailSender = new EmailSender();
    	this._footer = footer;
    }
    public BulkEmailSender(IEmailSenderFactory
    	factory, string footer) {
    	this._emailSender = factory.CreateNew();
    	this._footer = footer;
    }

    Dove:

    interface IEmailSender {
    	bool SendEmail(string to, string body);
    }
    interface IEmailSenderFactory {
    	IEmailSender CreateNew();
    }
    // e, per esempio, una factory è:
    class EmailSenderFactory : IEmailSenderFactory {
    	public IEmailSender CreateNew() {
    	return new EmailSender();
    	}
    }

     

    Altro esempio di (non static) Factory

    public class C {
    	private readonly IPointFactory _pointFactory;
    	private readonly ILineFactory _lineFactory;
    	public C(IPointFactory pointFactory, ILineFactory lineFactory) {
    		this._pointFactory = pointFactory;
    		this._lineFactory = lineFactory;
    }
    public void DoSomething(/* ... */) {
    	// ...
    	ILine newLine = CreateLine(/* ... */);
    	// ...
    }
    private ILine CreateLine(int x0, int y0, int x1, int y1) {
    	IPoint p0 = this._pointFactory.Create(x0, y0);
    	IPoint p1 = this._pointFactory.Create(x1, y1);
    	return this._lineFactory.Create(p0, p1);
    	}
    }

     

    Diventa facile sostituire un'implementazione con un'altra

    Di conseguenza, diventa più facile il testing ed estendere le funzionalità, per esempio, Decorator

     

    Esempio di Decorator

    class LoggingEmailSender : IEmailSender {
    	private readonly IEmailSender _originalSender;
    	public LoggingEmailSender(IEmailSender originalSender) {
    		if (originalSender == null)
    		throw new ArgumentNullException(nameof(originalSender);
    		this._originalSender = originalSender;
    }
    public bool SendEmail(string to, string body) {
    	Console.WriteLine("Sending mail to {0}", to);
    	return this._originalSender.SendEmail(to, body);
    	}
    }

     

    Principio di minima conoscenza

    public void Purchase(Customer c) {
    	Money m=c.GetWallet().GetMoney();
      this.RecordSale(..., m);
    }
    // Nel testing:
    Money m = new Money(5);
    Wallet w = new Wallet(m);
    Customer c = new Customer(w);
    Goods g = g.Purchase(c);
    //assert

     

    Versione tradizionale con singleton

    DB.Init("..."); //inizializza il singleton
    Logger.Init(); //idem, l'ordine è importante!
    var bulk=new BulkEmailSender();
    //apparentemente indipendente da sopra
    //ma se il Logger non è stato inizializzato
    //EmailSender (istanziato da BulkEmailSender)
    //si shchianta

    con la DI

    vard db=new DB(...);
    var logger=new Logger(db);
    var emailSender=new EmailSender(logger);
    var bulk=new BulkEmailSender(emailSender, "");

    sbagliando l'ordine la compilazione fallisce!

     

    DI container

    Siccome le dipendenze sono esplicite, la composizione degli oggetti spesso si può automatizzare, è quello che fanno i container DI

    Si programmano associando implementazioni a interfacce:

     - via codice, max flessibilità e controllo statico sui tipi, ma la riconfigurazione richiede ricompilazione

     - via file di configurazione (tipicamente XML)

    E poi loro gestiscono la creazione e lo scope (singleton, factory o container)

     

    Non è necessario usare un DI container per usare la DI

    Come per esempio non è necessario usare una libreria di logging per "loggare"

    Le implementazioni non mancano: Ninject, Autofac, Guice, NanoContainer, Spring.NET, Unity,..

     

    Inversion of Control e Dependency Injection sono la stessa cosa?

    IoC è un concetto più generale

     - Ogni framework usa IoC (quando associate un'azione a un pulsante in una GUI lo state usando)

    DI è il nome più corretto

     

    Ninject

    Un framework per le dependency injection open source per .NET

     

    E una maniera di disaccoppiare le classi e rendere più facili il riuso del codice, cambiando le implementazioni dell'interfaccia, rendere il codice più testabile e mantenere il codice

     

    DI Container

    Una libreria che automatizza molti dei task per creare e comporre degli oggetti.

    Ninject è un framework open source, i vantaggi sono tanti, è ancora in sviluppo, è veloce e non pesa molto, potente fornisce tutte le funzionalità ed è facile da utilizzare

    Official site: http://www.ninject.org

    Disponibile anche tramite NuGet

     

    Vediamo l'esempio citato sopra

    public interface IEmailSender {
    	bool SendEmail(string to, string body);
    }
    public class AnEmailSender : IEmailSender { /∗ ... ∗/ }
    
    public class BulkEmailSender {
    	private readonly IEmailSender emailSender;
    	private readonly string footer;
      
    	public BulkEmailSender(IEmailSender emailSender /∗, string footer∗/) {
    		this. emailSender = emailSender;
    		this. footer = String.Empty;
    	}
    public void SendEmail(List<string> addresses, string body) { /∗ ... ∗/ }
    }

     

    Come faccio a usare Ninject?

    Costruisco il kernel, un oggetto centrale

    Ikernel kernel=new StandardKernel();

    dico al nucleo quali sono le classi che mi interessano

    kernel.Bind<IEmailSender>().To<AnEmailSender>();

    richiedo un istanza del servizio

    var bulkES=kernel.Get<BulkEmailSender>();

    usiamo il servizio

    bulkES.SEndEmail(new List<string> {"[email protected]", "[email protected]"}, "hi");

     

    La capacità di associare il tipo del servizio (interfacce o classe astratte) al tipo di implementazione del mio servizio

     

    Tipato fortemente o debolmente

    kernel.Bind<IEmailSender>().To<AnEmailSender>(); // ok
    kernel.Bind<IEmailSender>().To<string>(); // compilation error
    
    kernel.Bind(typeof(IEmailSender)).To(typeof(AnEmailSender)); // ok
    kernel.Bind(typeof(IEmailSender)).To(typeof(string)); // ”ok”

     

    Le classi sono per default legate a se stesse

    kernel.Bind<BulkEmailSender>().To<BulkEmailSender>();
    kernel.Bind<BulkEmailSender>().ToSelf();

     

    Injection Patterns

     

    Ninject supporta due forme i injection:

    Constructor Injection

    Inizialization methods

     

    Costruire l'oggetto, qual è l'ambito in cui l'oggetto deve essere ancora vivo,  e si usano i metodi che finoscono con scope.

    Transient -> .InTransientScope() -> una nuova istanza ogni volta (equivalente di new)

    Singleton -> .InSingletonScope() -> all'interno esiste una sola istanza (creato l'oggetto e viene richiesto sempre e solo questo)

    Thread -> .InThreadScope() -> una istanza per thread

    Request -> .InRequestScope() -> una istanza per la durata del web request

    Custom -> .InScope() -> definire una funzione e quale oggetto venga restituito a seconda del contesto in esecuzione

     

    kernel.Bind<IEmailSender>().To<AnEmailSender>().InTransientScope();
    kernel.Bind<BulkEmailSender>().ToSelf().InSingleTonScope();
    var b1=kernel.Get<BulkEmailSender();
    var b2=kernel.Get<BulkEmailSender>();
    Debug.Assert(ReferenceEquals(b1,b2));
    var e1=kernel.Get<IEmailSender>();
    var e2=kernel.Get<IEmailSender>();
    Debug.Assert(!RefernceEquals(e1.e2));

     

    Torniamo al nostro esempio:

    public interface IEmailSender {
      bool SendEmail(string to, string body);
    }
    public class BulkEmailSender {
      private readonly IEmailSender _emailSender;
      private readonly string _footer;
      public BulkEmailSender (IEmailSender emailSender, string footer) {
        this._emailSender=emailSender;
        this._footer=footer;
      }
      public void SendEmail(List<string> addresses, string body) { /*...*/ }
    }
    public class AnEmailSender : IEmailSender { /*...*/ }

    Ora, kernel.Get<BulkEmailSender>() non funziona più

     

    - Passare argomenti alla Get<>()

    kernel.Get<BulkEmailSender>(new ConstructorArgument("footer","Bye");

    - aggiustare l'argomento

    //usando WithConstructorArgument
    kernel.Bind<BulkEmailSender>().ToSelf().WithConstructorArgument("footer","my Footer");
    //delegate crendo nuova istanza
    kernel.Bind<BulkEmailSender>().ToMethod(context => new BulkEmaailSender(context.kernel.Get<IEmailSender>(),"my footer"));

     

    Refactory esempio:

    public interface IEmailSenderFactory : IEmailSenderFactory
    	public IEmailSender Creat() {
    		return new AnEmailSender();
    	}
    }
    public class IBulkEmailSenderFactory {
      IBulkEmailSender Create(string footer);
    }

    implementazioni:

    public class EmailSenderFactory : IEmailSenderFactory {
    	public IEmailSender Create() {
    		return new AnEmailSender();
    	}
    }
    public class BulkEmailSenderFactory : IBulkEmailSenderFactory {
    	private readonly IEmailSenderFactory emailSenderFactory;
    public BulkEmailSenderFactory(IEmailSenderFactory emailSenderFactory) {
    	this. emailSenderFactory = emailSenderFactory;
    }
    public IBulkEmailSender Create(string footer) {
    	return new BulkEmailSender(this. emailSenderFactory.Create(), footer);
    	}
    }

    collego il kernel

    IKernel kernel = new StandardKernel();
    kernel.Bind<IEmailSender>().To<AnEmailSender>();
    kernel.Bind<IEmailSenderFactory>().To<EmailSenderFactory>().InSingletonScope();
    kernel.Bind<IBulkEmailSenderFactory>().To<BulkEmailSenderFactory>().InSingletonScope();
    // ...
    var factory = kernel.Get<IBulkEmailSenderFactory>();
    var bulkEmailSender = factory.Create("Bye");

     

    Multi Injection

    public interface IWeapon {
    	string Hit(string target);
    }
    public class Sword : IWeapon {
    	public string Hit(string target) { return "Slice " + target + " in half"; } }
    public class Dagger : IWeapon {
    	public string Hit(string target) { return "Stab " + target + " to death"; } }
    //...
    static void Main(string[] args) {
    	IKernel kernel = new StandardKernel();
    	kernel.Bind<IWeapon>().To<Sword>();
    	kernel.Bind<IWeapon>().To<Dagger>();
    	var weapon = kernel.Get<IWeapon>(); // error: which one?
    	IEnumerable<IWeapon> weapons = kernel.GetAll<IWeapon>();
    	foreach (var w in weapons)
    		Console.WriteLine(w.Hit(”bad guy”));

     

    Se la classe Samurai riceve un IWeapon[], List<IWeapon> o IEnumerable<IWeapon>

    public class Samurai {
    	private readonly IEnumerable<IWeapon> allWeapons;
    public Samurai(IWeapon[] allWeapons) {
    	this. allWeapons = allWeapons;
    }
    public void Attack(string target) {
    	foreach (var weapon in this. allWeapons)
    	Console.WriteLine(weapon.Hit(target));
    }

     

    dopo dobbiamo fare:

    kernel.Get<Samurai>().Attack("your enemy");

     

     Share


    User Feedback

    Recommended Comments

    There are no comments to display.


×
×
  • Create New...

Important Information

Terms of Use Privacy Policy Guidelines We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.