Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Archive for February, 2008

Quando Implementare Interfacce Esplicitamente in C#

Posted by Ricibald on 25th February 2008

C# consente di implementare esplicitamente interfacce. Secondo quando dichiarato da Microsoft, l’implementazione esplicita consente di evitare ambiguità provenienti da implementazioni di due classi aventi un metodo con la stessa signature. In realtà la potenzialità dell’approccio mi sembra un po’ sminuita:

L’implementazione esplicita di un membro di interfaccia consente di non inquinare la classe che implementa l’interfaccia con metodi che escono fuori dalla sua logica o che risultano poco utili per il client della classe

I due punti significano:

  • non inquinare interfaccia con metodi che risultano poco utili all’utilizzatore:
    • la classe “Studente” deve essere ordinabile per matricola. Per queso implementa IComparable. I metodi di IComparable verranno utilizzate nelle collezioni per ordinare, ma per chi utilizza la classe Studente (ad esempio “Segreteria“) non interessa avere un metodo “CompareTo“. Per questo conviene implementare esplicitamente l’interfaccia IComparable
  • non inquinare interfaccia con metodi che escono fuori dalla logica della classe:
    • in realtà è un caso particolare della prima, ma da trattare esplicitamente. Prendiamo il caso del design pattern Composite. L’interfaccia Component dovrebbe essere uniforme sia per oggetti Composite che per oggetti Leaf, ma i metodi addChild e removeChild non hanno senso per un oggetto Leaf. Ciò nonostante sono necessari per garantire un utilizzo generico della classe Component: un addChild in un Leaf non produrrà niente. L’utilizzo di Component è utile per gestire quindi oggetti compositi, ma per un client che utilizza Leaf risulta ambiguo vedere metodi addChild. Per questo è necessario implementare esplicitamente Component in Leaf, mentre in Composite dovrebbe essere implementata normalment

Posted in .net | No Comments »

Delegati Anonimi vs Command

Posted by Ricibald on 18th February 2008

In ASP.NET normalmente i comandi vengono assegnati tramite la creazione di un delegato che risponde all’evento “click” nel seguente modo:

void Page_Load(object sender, EventArgs e) {
    ButtonStudente.Click += new EventHandler(this.Studente_Click);
}
void Studente_Click(object sender, EventArgs e) {
    LabelMatricola.Text = this.Studente.Matricola;
}

Ma nel caso di molteplici pulsanti creati dinamicamente, allora “sarebbe” conveniente utilizzare le proprietà CommandName e CommandArgument, implementazioni “blande” del pattern Command in ASP.NET:

void Page_Load(object sender, EventArgs e) {
    ...
    foreach(Studente s in studenti) {
        Button buttonStudente = new Button();
        buttonStudente.CommandName = "GetMatricola";
        buttonStudente.CommandArgument = s.Matricola;
        buttonStudente.Command += new CommandEventHandler(this.Studente_Click);
    }
    ...
}
void Studente_Click(object sender, CommandEventArgs e)  {
    LabelMatricola.Text = (string)e.CommandArgument;
}

L’approccio è fattibile, ma fragile:

  • parametri passati come oggetto generico da convertire
    • se devo passare una lista di parametri?
      • bisognerebbe creare la versione tipizzata della lista di argomenti
  • possibili errori di cast
  • possibili errori nella scansione del tipo di comando da trattare
  • comportamento distribuito tra cicli e metodi con oggetti da convertire

Per situazioni come queste conviene invece utilizzare tutta la potenza dei delegati anonimi come segue:

void Page_Load(object sender, EventArgs e) {
    ...
    foreach(Studente s in studenti) {
        Button buttonStudente = new Button();
        buttonStudente.Click += new EventHandler(delegate(object sender, EventArgs e) {
            LabelMatricola.Text = s.Matricola;
        });
    }
    ...
}

L’approccio diventa elegante e facilmente comprensibile. Notare che la funzione viene solo dichiarata ed eseguita solo se il click avviene.

Posted in .net | 4 Comments »

Invocare un Metodo con una Culture Personalizzata

Posted by Ricibald on 7th February 2008

Per formattare le date o i numeri in un certo modo si possono utilizzare le tecniche di formattazione.

Quindi, se ad esempio vogliamo esprimere la data in un formato particolare, possiamo utilizzare:

DateTime date = DateTime.Now;
string format = date.ToString("MM/dd/yyyy g");

Per maggiore flessibilità possiamo immaginare di inserire il formato in una chiave di configurazione e il gioco è fatto.

Ma ora immaginiamo che ci arrivi una nuova richiesta dal nostro fido cliente:

Voglio i numeri a virgola mobile formattate come “xx.yy”.

Ora, dovremmo quindi andare a modificare tutti i double come:

double d = 2.2;
string format = d.ToString("00.00");

Il problema è quindi che le modifiche ai formati non sono sotto controllo. Esistono due modi per mantenerli sotto controllo, utilizzando le CultureInfo:

  • invocare dove possibile ToString(IFormatProvider), passando una CultureInfo creata al momento dell’inizializzazione
    • ma richiede un’accortezza di chi scrive codice
    • …e una bella scocciatura…
  • specificare la CultureInfo dell’assembly nel file di configurazione
    • deve essere o standard o creata appositamente e registrata
      • chi la crea? chi la registra?
    • vincolo tutto il progetto ad utilizzare quella CultureInfo: come ripristinare il comportamento originario?

Dall’altra parte è vero che la CultureInfo del thread corrente potrebbe essere impostata come si vuole, ma bisogna anche fare i conti con il multi-threading, specialmente vero in applicazioni a interfaccia grafica o web.

Bisognerebbe quindi:

creare un thread con l’unico scopo di isolare il cambiamento di CultureInfo

Si vuole quindi rendere disponibile una funzione che in modo generico faccia questo:

class BilancioCreator {
    public Bilancio CreaBilancio(int annoBilancio)
    {
        return CustomCultureInfoInvoker.InvokeMethod<Bilancio>(new CustomCultureInfo(), delegate()
        {
            return CreaBilancioImpl(annoBilancio);
        });
    }

    private Bilancio CreaBilancioImpl(int annoBilancio)
    {
        string data = DateTime.Now;
        Bilancio result = new Bilancio(data.ToString());
        result.CalcolaBilancio(annoBilancio);
        return result;
    }
}

class CustomCultureInfo : CultureInfo
{
    public CustomCultureInfo()
        : base("en-US")
    {
        this.NumberFormat.CurrencyDecimalSeparator = ".";
        this.NumberFormat.CurrencyDecimalDigits = 0;
        this.NumberFormat.CurrencyGroupSeparator = "";
        this.NumberFormat.CurrencyNegativePattern = 5;
        this.NumberFormat.CurrencySymbol = "";
        this.NumberFormat.NumberDecimalDigits = 0;

        /* Il DateTime.ToString combina ShortDatePattern con LongTimePattern */
        this.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
        this.DateTimeFormat.LongTimePattern = "";
    }
}

Si mette quindi a disposizione il codice che consente questo, per chiunque ne fosse interessato.

public static class CustomCultureInfoInvoker
{
    public delegate T Method<T>();
    public static T InvokeMethod<T>(CultureInfo culture, Method<T> method)
    {
        Exception exceptionThread = null;
        T result = default(T);
        Thread customCultureThread = new Thread(delegate()
        {
            try
            {
                result = method.Invoke();
            }
            catch (Exception ex)
            {
                exceptionThread = ex;
            }
        });
        customCultureThread.CurrentCulture = culture;
        customCultureThread.CurrentUICulture = culture;
        customCultureThread.Start();
        customCultureThread.Join();

        /* Rilancia eventuali errori */
        if (exceptionThread != null)
        {
            throw new Exception("Errore: " + exceptionThread.Message, exceptionThread);
        }
        return result;
    }
}

Notare che per i valori “decimal” il discorso è un po’ diverso, poiché in questo caso la CultureInfo non consente di fare proprio tutto. Per questo si veda il precedente post per cambiare blocchi di valori dello stesso tipo.

Posted in .net | No Comments »

Collezioni di Sola Lettura (Read-Only) in .NET

Posted by Ricibald on 7th February 2008

Per utilizzare collezioni readonly bisognerebbe utilizzare il metodo:

List<T>.AsReadOnly()

Avvolge la lista in modo da impedirne la modifica. Ha complessità O(1). Quindi non esegue una copia della lista, ma rende la lista puntata read-only.
Un utilizzo pratico è quindi:

class Universita
{
private IList<Studente> _studenti = new List<Studente>();

public IList<Studente> Studenti
{
get
{
return _studenti.AsReadOnly();
}
}

public void AddStudente(Studente studente) {
_studenti.Add(studente);
}
}

Risulta quindi modificabile dentro la classe, ma non modificabile per i clienti della classe. Attenzione però: per non modificabile si intende che la collezione non può essere modificata, ma le proprietà dei suoi elementi invece potrebbero essere modificate (il nome di uno studente potrebbe essere modificato).

Per questo la visibilità delle proprietà “set” deve essere opportunamente progettata, in modo che la classe Università non veda i metodi per impostare le proprietà di Studente.

Posted in .net | 3 Comments »

Troncare valori Decimali Assimilabili ad Interi: Automatizzare Modifiche di Proprietà in una Classe

Posted by Ricibald on 1st February 2008

Lo so, il titolo dice poco e dice troppo… Cerco ora di spiegarvi la cosa, e credo che possa interessare a tutti gli sviluppatori .NET.

Mi sono trovato con questo problema apparentemente banale:

Dal database mi venivano restituiti valori di tipo Money, che venivano memorizzati come Decimal in una classe. La conversione Money-Decimal si porta dietro 4 cifre decimali, a prescindere dal valore. Vorrei quindi troncare queste cifre quando non serve. Ad esempio, vorrei convertire 12000,0000 in 12000.

Problema semplice, ma come? Considerate che non avevo un solo decimal, ma circa 200 valori memorizzati così. La soluzione immediata è utilizzare (12000.0000m).ToString("F") o Decimal.Truncate(12000.0000m) per troncare le cifre decimali, ma questo dovrei scriverlo 200 volte, e odio fare copia e incolla “sintomo di cattiva programmazione”.

Allora quello che mi serviva era:

Creare un metodo di utilità che mi consentisse di modificare tutte le cifre decimali in tutto il grafo degli oggetti che conteneva il mio oggetto di partenza.

Vorrei dunque creare un metodo come questo:

Bilancio bilancio = ImportBilancio();
PropertyModifier.ApplyToAllOfType<decimal>(bilancio, delegate(decimal number)
{
    decimal numberTruncated = Decimal.Truncate(number);
    Debug.Write(String.Format(" [Troncato: {0} -> {1}]", number.ToString(), numberTruncated.ToString()));
    return numberTruncated;
});

Riporto quindi la classe che consente questa utilissima funzione, per chiunque ne avesse bisogno:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;

namespace PropertyModifier
{
public class PropertyModifier
{
public delegate T MethodModifier(T t);

private static BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

public static void ApplyToAllOfType(object obj, MethodModifier modifier)
{
ApplyToAllOfType(obj, modifier, 0, true);
}

private static void ApplyToAllOfType(object obj, MethodModifier modifier, int indent, bool printCurrent)
{
if (obj == null)
return;

Type objType = obj.GetType();

if (printCurrent)
{
DebugWriteNode(objType.Name, indent);
Debug.WriteLine(“”); // vai a capo
}

/* Scandisci proprieta’ */
foreach (PropertyInfo prop in objType.GetProperties(flags))
{
/* Se e’ leggibile e non e’ indexed, ottieni valore */
if (prop.CanRead && prop.CanWrite && prop.GetGetMethod().GetParameters().Length == 0)
{
object propValue = prop.GetValue(obj, null);
if (propValue != null && !propValue.Equals(obj)) // previene cicli
{
DebugWriteNode(String.Format(“p:{0}={1}”,prop.Name, propValue), indent);
if (propValue is T1)
{
object modifiedValue = modifier((T1)propValue);
prop.SetValue(obj, modifiedValue, null);
}
Debug.WriteLine(“”); // vai a capo
ApplyToAllOfType(propValue, modifier, indent + 1, false);
}
}
}

/* Scandisci campi */
foreach (FieldInfo field in objType.GetFields(flags))
{
if (!field.IsInitOnly && !field.IsLiteral)
{
object fieldValue = field.GetValue(obj);
if (fieldValue != null && !fieldValue.Equals(obj)) // previene cicli
{
DebugWriteNode(String.Format(“f:{0}={1}”, field.Name, fieldValue), indent);
if (fieldValue is T1)
{
object modifiedValue = modifier((T1)fieldValue);
field.SetValue(obj, modifiedValue);
}
Debug.WriteLine(“”); // vai a capo
ApplyToAllOfType(fieldValue, modifier, indent + 1, false);
}
}
}

/* Se ho un elemento enumerabile, prendi gli elementi */
if (obj is IEnumerable)
{
IEnumerable enumerableObj = (IEnumerable)obj;
int currElement = 1;
foreach (object childObj in enumerableObj)
{
DebugWriteNode(String.Format(“{0}:{1}={2}”, currElement++, objType.Name, childObj), indent);
Debug.WriteLine(“”); // vai a capo
ApplyToAllOfType(childObj, modifier, indent + 1, false);
}
}
}

private static void DebugWriteNode(string nameToPrint, int indent)
{
for (int i = 0; i < indent; i++) { Debug.Write("| "); } Debug.Write("+ " + nameToPrint); } } } [/sourcecode]

Posted in .net | 2 Comments »