Programming Languages Hacks

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

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

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 »

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 »