Una soluzione che è venuto in mente quando ho letto questo è stato quello di utilizzare il supporto integrato per Attributes in C#. Un attributo è un modo di codificare una proprietà, un campo, un metodo, una classe, ecc. Con alcuni metadati aggiuntivi che vengono poi utilizzati da un'altra classe, ad esempio durante Serialization. Lo vedrai più spesso.
Avevo un'applicazione che stavo costruendo che doveva essere in grado di prendere una collezione di oggetti IEnumerable
e produrre alcuni dati in un file in base alle scelte selezionate dall'utente. Ho creato una classe di attributi che mi ha dato la possibilità di leggere le scelte tramite la riflessione e agire come indicato.Lasciate che vi mostri l'esempio:
prima classe di attributo:
[System.AttributeUsage(AttributeTargets.Property)]
class ExportOptionsAttribute : System.Attribute
{
public string Header { get; set; }
public string FormatString { get; set; }
public bool Export { get; set; }
public int Order { get; set; }
/// <summary>
///
/// </summary>
/// <param name="header"></param>
public ExportOptionsAttribute(string header) : this (header, null, true)
{
}
/// <summary>
///
/// </summary>
/// <param name="header"></param>
/// <param name="formatString"></param>
/// <param name="export"></param>
public ExportOptionsAttribute(string header, string formatString, bool export)
{
this.Header = header;
this.FormatString = formatString;
this.Export = export;
this.Order = 0;
}
}
Con questa classe definita in questo modo, ho potuto decorare la mia proprietà di classe di dati come questo (reali proprietà modificate in modo da non perdersi su il business gergo):
public sealed class PartsOrder
{
/// <summary>
///
/// </summary>
[ExportOptions("Customer Name", Order=0)]
public string CustomerName { get; set; }
/// <summary>
///
/// </summary>
[ExportOptions("Catalog Name", Order = 1)]
public string Catalog Name { get; set; }
/// <summary>
///
/// </summary>
[ExportOptions("Unit", Order = 2)]
public string Unit { get; set; }
/// <summary>
///
/// </summary>
[ExportOptions("Component", Order = 3)]
public string Component { get; set; }
/// <summary>
///
/// </summary>
[ExportOptions("Delivery Point", Order = 4)]
public string DeliveryPoint { get; set; }
/// <summary>
///
/// </summary>
[ExportOptions("Order Date", Order = 5)]
public string OrderDate { get; set; }
}
allora nella mia routine di esportazione, invece di codificare i nomi delle proprietà, che sono variabili, o passando una struttura dati complessa attorno alla quale contiene le informazioni su quali campi per mostrare o nascondere e che ordine era, io è sufficiente eseguire il codice seguente, utilizzando reflection, per eseguire il loop delle proprietà e generare i relativi valori, in questo caso in un file CSV.
StringBuilder outputDoc = new StringBuilder();
// loop through the headers in the attributes
// a struct which decomposes the information gleaned from the attributes
List<OrderedProperties> orderedProperties = new List<OrderedProperties>();
// get the properties for my object
PropertyInfo[] props =
(typeof(PartsOrder)).GetProperties();
// loop the properties
foreach (PropertyInfo prop in props)
{
// check for a custom attribute
if (prop.GetCustomAttributesData().Count() > 0)
{
foreach (object o in prop.GetCustomAttributes(false))
{
ExportOptionsAttribute exoa = o as ExportOptionsAttribute;
if (exoa != null)
{
orderedProperties.Add(new OrderedProperties() { OrderByValue = exoa.Order, PropertyName = prop.Name, Header = exoa.Header, Export = exoa.Export });
}
}
}
}
orderedProperties = orderedProperties.Where(op => op.Export == true).OrderBy(op => op.OrderByValue).ThenBy(op => op.PropertyName).ToList();
foreach (var a in orderedProperties)
{
outputDoc.AppendFormat("{0},", a.Header);
}
// remove the trailing commma and append a new line
outputDoc.Remove(outputDoc.Length - 1, 1);
outputDoc.AppendFormat("\n");
var PartsOrderType = typeof(PartsOrder);
//TODO: loop rows
foreach (PartsOrder price in this.Orders)
{
foreach (OrderedProperties op in orderedProperties)
{
// invokes the property on the object without knowing the name of the property
outputDoc.AppendFormat("{0},", PartsOrderType.InvokeMember(op.PropertyName, BindingFlags.GetProperty, null, price, null));
}
// remove the trailing comma and append a new line
outputDoc.Remove(outputDoc.Length - 1, 1);
outputDoc.AppendFormat("\n");
}
Il codice per l'OrderedProperties struct è qui:
struct OrderedProperties
{
/// <summary>
///
/// </summary>
public int OrderByValue;
/// <summary>
///
/// </summary>
public string PropertyName;
/// <summary>
///
/// </summary>
public string Header;
/// <summary>
///
/// </summary>
public bool Export;
}
Come si può vedere, la logica per estrarre i valori delle proprietà è completamente ignaro della struttura della classe. Tutto ciò che fa è trovare le proprietà che sono decorate con l'attributo che ho creato e usarlo per guidare l'elaborazione.
Spero che tutto abbia un senso, e se hai bisogno di più aiuto o chiarimenti, non esitare a chiedere.
Hai visto il Managed Extensibility Framework (MEF)? http://mef.codeplex.com/ http://msdn.microsoft.com/en-us/library/dd460648.aspx – spender
Ne ho sentito parlare e ho una breve idea del suo utilizzo. Il fatto è che sono nuovo alla programmazione e voglio implementare una soluzione che sia facilmente trasferibile ad altri linguaggi e sistemi. Inoltre, voglio davvero imparare a implementare questi sistemi complessi. –
L'implementazione in C# la rende portabile ad altri sistemi in quanto vi è Mono, che è una porta del framework .Net che gira su Linux e altre piattaforme. Non mi preoccuperei necessariamente di renderlo così generico anche se portasse rapidamente in altre lingue. Ogni lingua e piattaforma ha il proprio set di idiomi e best practice e qualcosa che funziona bene in uno potrebbe non funzionare bene in un altro. –