Lavorando spesso con gli ArcObjects, in particolare avendo a che fare con le singole Feature, capita di frequente di volerne leggere gli attributi per eseguire elaborazioni, controlli o quant’altro.

In seguito vedremo come estendere IFeature per semplificarci la vita.

Normalmente il procedimento da seguire è abbastanza standard, si parte da una IFeature che può, ad esempio, provenire dalla selezione in mappa di ArcMap

IFeature pFeature = LoadFeature();

Il passo successivo è individuare l’indice del campo che vogliamo consultare; in quanto interfaccia generica, una IFeature non espone direttamente gli attributi

int iField = pFeature.Fields.FindField("foo");

A questo punto se l’indice è >= 0 (ovvero se il campo esiste veramente) possiamo leggere il valore del campo, che però presenta un ulteriore problema: il metodo get_Value(int Index) delle IFeature ritorna un tipo generico object.

object fieldValue = pFeature.get_Value(iField);

Per essere utilizzabile bisogna quindi castarlo di tipo in base al tipo del campo letto, operazione non effettuabile se il valore è DBNull, controlli che vanno ad aggiungere ulteriore complessità ad un’operazione di routine utilizzata spesso.

La prima soluzione che viene in mente è quella di creare una funzione statica, in una qualche libreria d’appoggio che si occupi di fare il lavoro per noi, evitando di riscrivere ogni volta lo stesso codice ed eseguire gli stessi controlli. Questa soluzione è ottimale, assolve egregiamente il suo compito, ma presenta alcuni problemi che vedremo in seguito.

public static object getValue(IFeature pFeature, string field)
{
	if (pFeature == null)
	{
		return null;
	}
 
	int iField = pFeature.Fields.FindField(field);
 
	if (iField == -1)
	{
		return null;
	}
 
	object fieldValue = pFeature.get_Value(iField);
 
	if (fieldValue == null || fieldValue == DBNull.Value)
	{
		return null;
	}
 
	return fieldValue;
}

Benché funzionale, rimangono alcuni problemi/considerazioni:

  1. Il risultato ritornato è sempre di tipo object e andrà quindi castato all’esterno della funzione
  2. La funzione statica andrà aggiunta ad una libreria, quindi andrà necessariamente richiamata
  3. Gestire il valore da utilizzare in caso di null è un’operazione che andrà gestita all’esterno della funzione in base al campo e al tipo di campo letto, aggiungendo righe di codice sotto forma di condizioni
  4. E’ possibile sostituire i return null con una opportuna funzione che ritorni un valore di default in base al tipo di campo, ma questa soluzione può essere un’arma a doppio taglio
In nostro soccorso per fortuna arriva il Framework .NET, regalandoci i Metodi di Estensione e le Interfacce Generiche.
Sfruttando queste funzionalità è possibile creare un’estensione direttamente alle IFeature aggiungendo un nuovo metodo, che si occuperà di leggere il valore dell’attributo desiderato ed anche di gestire il casting di tipo. Andremo quindi a creare nel nostro progetto una nuova classe statica (preferibilmente in un file dedicato alla quale sarà possibile aggiungere anche altre estensioni)
public static class Extensions
{
}

All’interno della nostra classe andremo a definire il metodo di estensione

public static T getValue<T>(this IFeature pFeature, string field)
{
}

La chiave this si occupa di dire che stiamo estendendo le IFeature, mentre la chiave T si occupa di dichiarare l’output della nostra funzione come generico. A questo punto non rimane che modificare la funzione di prima in modo da sfruttare la potenza delle Interfacce Generiche.

public static T getValue<T>(this IFeature pFeature, string field)
{
	if (pFeature == null)
		return default(T);
 
	int iField = pFeature.Fields.FindField(field);
 
	if (iField < 0)
		return default(T);
 
	object fieldValue = pFeature.get_Value(iField);
 
	if (fieldValue == DBNull.Value)
		return default(T);
 
	return (T)fieldValue;
}

Con questo metodo, andiamo a gestire tramite default(T) il valore di default da tornare per i campi nulli, che sarà il valore di default per il tipo letto (ad esempio per int è 0); inoltre il valore finale ritornato dal nostro metodo, in caso di valore non nullo, è castato di tipo direttamente dalla nostra funzione. Ma come si usa tutto questo?

int foo = pFeature.getValue<int>("foo");

Da questo momento tutti gli oggetti di tipo IFeature esporranno in automatico il metodo getValue, al quale dovremo solo specificare il tipo e il campo da leggere; ma come gestire a nostro piacimento i valori di default? Nel caso in cui il campo foo sia null, il valore ritornato da quella funzione sarebbe 0. Possiamo risolvere il tutto tramite i Tipi Nullabili

int foo = pFeature.getValue<int?>("foo") ?? 42;

Dichiarando come tipo del campo un intero nullabile, il suo valore di default è null; grazie all’ operatore ?? è possibile poi specificare quale valore assegnare in caso di nullo, a seconda delle necessità.

Conclusioni

Questo codice funziona a partire dal framework .NET 3.0 in avanti, abbiamo visto come in maniera semplice e immediata possiamo aggiungere metodi agli ArcObjects per velocizzare e semplificare il nostro lavoro.

Nel prossimo articolo parleremo di come estendere IFeatureClass in modo da poter sfruttare gli iteratori.

   
© 2011 gismart by sinergis Suffusion theme by Sayontan Sinha