Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I have a program that has to loop throug a very large amount of features. I have created a wrapper for the feature. This made me think: How expensive is the Fields.FindField method? Would it be faster - and if so; how much - to store the field index in a static field in the wrapper class. An example:

internal class XFeatureWrapper
{
    private static int XColumnIndex =-99;
    private string _xValue; 

    public FeatureWrapper(IFeature xFeature)
    {
        if (XColumnIndex == -99)
        {
            XColumnIndex = xFeature.Fields.FindField("XXX");
        }

        _xValue = xFeature.get_Value(XColumnIndex).ToString();

    }

    public string XValue
    {
        get { return _xValue; }
    }
}

Would this be faster than finding the field index in the feature every time?

Morten

share|improve this question
add comment

2 Answers

up vote 3 down vote accepted

Yes, you are expected to cache field indices to improve performance. Single field index lookup might take just few CPU ticks, but in tight loops, it is advisable to avoid doing so repeatedly.

This is especially true when working in .NET since the additional COM interop call marshalling will be more costly than the FindField method itself.

You can, however, have best of both worlds - precache field indices in a Dictionary<string, int>, then doing the lookup purely in .NET, which is faster than FindField. I have a class TableFields which has methods like object GetValue(IRowBuffer rowBuffer, string fieldName): it internally uses the dictionary (it caches the field index if not already cached for the field) and performance is very good. This way I do not do excessive field lookups using IFields.FindField and do not have methods cluttered with lot of local variables containing field indices.

EDIT: example of this approach:

public class TableFields
{
    private readonly IDictionary<string, int> _fieldIndices = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
    private readonly string _tableName; // for error reporting

    // even though these two vars are used in two methods only, they are declared here
    // to minimize excessive memory allocations
    private int _index;
    private object _value;

    ///<summary>
    /// Constructor.
    ///</summary>
    ///<param name="tableName">Table name. Used only for exception reporting..</param>
    ///<exception cref="ArgumentNullException">if <paramref name="tableName"/> is <b>null</b>.</exception>
    public TableFields(string tableName)
    {
        if (tableName == null) throw new ArgumentNullException("tableName");
        _tableName = tableName;
    }

    public object GetValueAsObject(IRowBuffer rowBuffer, string fieldName)
    {
        return GetValueAsObject(rowBuffer, fieldName, null);
    }

    public int GetValue(IRowBuffer rowBuffer, string fieldName, int defaultValue)
    {
        return Convert.ToInt32(GetValueAsObject(rowBuffer, fieldName, defaultValue));
    }

    ... convenience methods for other field types

    public object GetValueAsObject(IRowBuffer rowBuffer, string fieldName, object defaultValue)
    {

        if (_fieldIndices.ContainsKey(fieldName))
        {
            _index = _fieldIndices[fieldName];
        }
        else
        {
            _index = rowBuffer.Fields.FindField(fieldName);
            _fieldIndices.Add(fieldName, _index);
        }

        if (_index == -1)
        {
            throw new TableFieldNotFoundException(
                string.Format("{0}.{1}", _tableName, fieldName),
                string.Format("Field named {0} was not found in table {1}", fieldName, _tableName));
        }

        _value = rowBuffer.get_Value(_index);
        if ((_value == null) || (_value == DBNull.Value) || (_value.ToString().Length == 0))
        {
            return defaultValue;
        }

        return _value;
    }

    public void SetValue(IRowBuffer rowBuffer, string fieldName, object newValue)
    {

        if (_fieldIndices.ContainsKey(fieldName))
        {
            _index = _fieldIndices[fieldName];
        }
        else
        {
            _index = rowBuffer.Fields.FindField(fieldName);
            _fieldIndices.Add(fieldName, _index);
        }

        if (_index == -1)
        {
            throw new TableFieldNotFoundException(
                string.Format("{0}.{1}", _tableName, fieldName),
                string.Format("Field named {0} was not found in table {1}", fieldName, _tableName));
        }

        rowBuffer.set_Value(_index, newValue);
    }

}

Now, for every table you work with, you maintain an instance of this class. It allows you to Get/Set field values, keeping the indices cached.

share|improve this answer
    
Thank you for your quick response. I considered storing the indices like you suggest, but since the datasource comes from an external supplier, I do not want to make too many assumtions. –  Morten Mar 11 '11 at 9:09
add comment

Morten, by the way you are trying to do it, you will end up with a lot of static variables. If you have a 12 column feature class, then you will have 12 static variables to control the index of fields.

Take a look at at this approach, as suggested by petr k.

public class FieldMapping
{
    private IDictionary<string,int> _FieldMapping;

    public IDictionary<string,int> FieldMapping
    {
        get { return _FieldMapping; }
        set { _FieldMapping = value; }
    }

    public FieldMapping(IDicitionary<string,int> mapping)
    {
        _FieldMapping = mapping;
    }

    public FieldMapping()
    {
        _FieldMapping = new Dicitionary<string,int>();
    }

    public int GetFieldIndex(string fieldName)
    {
        int index = 0;
        if (!_FieldMapping.TryGetValue(fieldName,out index))
            return -1;
        else
            return index;
    }
}

Compose your wrapper class with one of these such:

public class FeatureWrapper
{
    protected FieldMapping _FieldMapping;
    protected IObject _EsriObject;

    public FeatureWrapper(IObject esriObj)
    {
        _EsriObject = esriObj
        _FieldMapping = new FieldMapping();
    }
}

public class Car : FeatureWrapper
{
    public string CarName
    {
        get { return _EsriObject.get_Value(_FieldMapping.GetFieldIndex("CARNAME")); }
        set { _EsriObject.set_Value(_FieldMapping.GetFieldIndex("CARNAME"),value); }
    }

    public Car(IObject esriObj)
        :base(esriObj)
    {
        _FieldMapping.Add(new KeyValuePair<string,int>("OBJECTID",0));
        _FieldMapping.Add(new KeyValuePair<string,int>("CARNAME",1));
        // and so on...
    }
}

You can even do this in a different fashion, such as creating a "populate field mappings method" and making it abstract in the FeatureWrapper class, making all child classes implementing it. After that, in the FeatureWrapper method, call it during it's construction and in every child class, call base.

EDIT Even better, just iterate all fields during construction of a single featureWrapper instance.

public class FeatureWrapper { protected FieldMapping _FieldMapping; protected IObject _EsriObject;

public FeatureWrapper(IObject esriObj)
{
    _EsriObject = esriObj
    _FieldMapping = new FieldMapping();

    for (int i = 0; i <= _EsriObject.Fields.FieldCount -1; i++)
    {
        _FieldMapping.Add(new KeyPairValue<string,int>(_EsriObject.Fields.get_Field(i).Name,i));
    }
}

}

share|improve this answer
    
Thanks for your input. I will definitely use this pattern in most cases. However, in this particular case I will stick with the static fields because: a) there are not many fields and b) I have no control over field names in source feature class. –  Morten Mar 11 '11 at 12:07
    
Since you don't have control, you can use the last snippet, which will cache the field name in the time of construction. You still will get indexes and field names. You example is similar to my first, which you will need to know the fieldnames in advance. The second snippet I provided will cache all fields, no matter that fields are thrown at the FieldMapper. –  George Mar 11 '11 at 12:40
    
Last snippet is ok but cannot be used with good performance in tight loops. Also I prefer to avoid keeping references to geodatabase objects (IObject) if not needed. See my edit for an alternative. –  Petr Krebs Mar 11 '11 at 13:46
    
True, your code is more perfomatic :P. Mine is just more convenient :P –  George Mar 11 '11 at 16:05
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.