views:

109

answers:

4

Initial situation:

I am working with a proprietary framework (ESRI's ArcGIS Engine) which I want to extend with some new functionality. I've chosen to use extension methods in C# for this.

Shown below are the parts of the framework API that are relevant to this question:

    +------------------------+                   IGeometry
    |  IFeature <interface>  |                   <interface>
    +------------------------+                       ^
    |  +Shape: IGeometry     |                       |
    +------------------------+             +---------+---------+
                                           |                   |
                                        IPoint              IPolygon
                                        <interface>         <interface>

What I want to do:

I want to write an extension method for IFeature that will allow the following:

IFeature featureWithPointShape   = ...,
         featureWithPolygonShape = ...;

// this should work:
featureWithPointShape.DoSomethingWithPointFeature();

// this would ideally raise a compile-time error:
featureWithPolygonShape.DoSomethingWithPointFeature();

The problem is that both point and polygon shapes (IPoint and IPolygon) are wrapped in the same type (IFeature), for which the extension method is defined. The extension method has to be on IFeature because I can only get from an IFeature towards its IGeometry, but not vice versa.


Question:

While the type of an IFeature object's Shape can easily be checked at run-time (see code example below), how could I achieve this type check at compile-time?

public static void DoSomethingWithPointFeature(this IFeature feature)
{
    if (!(feature.Shape is IPoint))
    {
        throw new NotSupportedException("Method accepts only point features!");
    }
    ...  // (do something useful here)
}

(Is there possibly any way to use a generic wrapper type for IFeature, e.g. FeatureWithShape<IPoint>, define the extension method on this wrapper type, and then somehow turn all IFeature objects into this wrapper type?)

+1  A: 

Make your IFeature interface generic too:

IFeature<IPoint>
IFeature<IPolygon>

Then you can set a constaint on the inner type of the IFeature.

Sebastian P.R. Gingter
I cannot change `IFeature`, as it's part of the proprietary framework.
stakx
Then you could do this: MyFeature<T> : IFeature - that way you work with a generic interface and you have the full compiler checking, whil your generic type also is an IFeature you can pass to the framework.
Sebastian P.R. Gingter
Thanks for your input so far. Unfortunately, this won't work, either, because `IFeature` objects are created by the framework. Since the framework doesn't know about `MyFeature<T>`, I would never get an instance of that type. And I don't know of any way to elevate an object to a derived type without putting a wrapper around it. (Do you, by any chance?)
stakx
+1  A: 

By definition, if you have an IFeature object then its Shape property can contain a value of any type that implements IGeometry. If you're in control of instantiation of the IFeature objects, then you can create your own generic class that implements IFeature or derive a class from a framework class that implements IFeature and then you can easily constrain the type of Shape. If you're not in control of instantiation of these objects, then you're probably stuck with a run-time check.

If you happen to be using .NET 4.0, then you could use code contracts. The static checker would give you a compile-time warning if your extension method had a pre-condition on the type of Shape.

Jack Leitch
+1 for suggesting Code Contracts. (I have used it before, with VS 2008 btw., but it didn't occur to me to use it in this particular case.) What you write is exactly what I feared. Since I'm not in control of `IFeature` instantiation, there might be no nicer solution to this problem than using Code Contracts.
stakx
Oh, I just noticed that Code Contracts won't work, either. If I want a compile-time warning about a possible run-time type mismatch, I *have to express the expected type in the code* somehow. `IFeature` alone will *never* be able to achieve this. Thank you for steering my thoughts in the right direction!
stakx
A: 

Interesting question, for me especially since I (have to) program with ArcObjects for a living.

Edit, warning: this approach doesn't work. It'll fail at runtime. I'll leave it here for shame.

Taking Sebastian P.R. Gingter's suggestion of interface inheritance and running with it, I've defined an interface IFeatureOf<T> that inherits IFeature. The only thing this new interface is good for, is adding more information when declaring Features.

But if you know in advance that you're dealing with point features, then you can declare these features as IFeatureOf<IPoint> and feed them to functions that expect features containing a point geometry.

You can of course still declare a feature from a polygon feature class as var notReallyAPointFeature = (IFeatureOf<IPoint>)myPolygonFeature;, but if you know the feature type in advance and use IFeatureOF<> to constrain it, you will get compile time errors if you feed it to specialized functions.

Small example below:

using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;

class Program
{
    public interface IFeatureOf<T> : IFeature { };

    public static void AcceptsAllFeatures(IFeature feature) {
        //do something, not caring about geometry type...
        return;
    }

    public static void AcceptsOnlyPointFeatures(IFeatureOf<IPoint> pointFeature) {
        IPoint pointGeometry = (IPoint)pointFeature.Shape; //constraint guarantees this is OK
        //do something with pointGeometry
        return;
    }

    static void Main(string[] args)
    {
        IFeature pointFeature = new FeatureClass(); //this is where you would read in a feature from your data set
        IFeature polylineFeature = new FeatureClass();
        var constainedPointFeature = (IFeatureOf<IPoint>)pointFeature;
        var constrainedPolylineFeature = (IFeatureOf<IPolyline>)polylineFeature;

        AcceptsAllFeatures(constainedPointFeature);             //OK
        AcceptsAllFeatures(constrainedPolylineFeature);         //OK
        AcceptsAllFeatures(pointFeature);                       //OK
        AcceptsAllFeatures(polylineFeature);                    //OK

        AcceptsOnlyPointFeatures(constainedPointFeature);       //OK

        AcceptsOnlyPointFeatures(constrainedPolylineFeature);   //Compile-time error: IFeatureOf<IPolyline> != IFeatureOf<IPoint>
        AcceptsOnlyPointFeatures(pointFeature);                 //Compile-time error: IFeature != IFeatureOf<something>
        AcceptsOnlyPointFeatures(polylineFeature);              //Compile-time error: IFeature != IFeatureOf<something>
    }
}
cfern
While this would be a terrific solution, it doesn't seem to work because of this crucial step: `var constainedPointFeature = (IFeatureOf<IPoint>)pointFeature;` -- the co-class behind `pointFeature` does not implement `IFeatureOf<IPoint>`, and so an `InvalidCastException` will be raised. At least that's what happens on my machine...?
stakx
@stakx: you're right. I need to read more about interfaces and inheritance. I'd like more type-safe arcobjects, but it's not going to be easy apparently.
cfern
I think custom features might be one way to do this.http://edndoc.esri.com/arcobjects/9.0/ExtendingArcObjects/Ch07/TreeCustomFeature.htmI'd be really interested in seeing a way to do this with C# instead of C++. I suppose if there's a way to do COM aggregation with C# it should be possible (COM_INTERFACE_ENTRY_AGGREGATE_BLIND). If that is possible, I think it would make a great wiki entry.
Kirk Kuykendall
+1  A: 

I don't think you can achieve this check at compile time with the IFeature interface from ArcObjects.

The geometry type depends on the definition of the featureclass that the feature is loaded from. You won't know this until run-time.

Neil Duxbury