1

I am working with an API called ArcObjects and .NET 4.5. I encountered the error

'object' does not contain a definition for 'AddValue'

while using one of the objects on the API, but I think this may be a more general .NET issue than one with this API. This is an error that occurs at runtime.

For some background, ArcObjects are all COM objects so the .NET libraries are just wrappers around those. When working with ArcObjects I am primarily working with a Geodatabase which is basicaly a regular RDBS (with a spatial component) where each row is called a feature.

My first attempt code was as follows:

//this IUniqueValueRenderer is an ArcObject
var uvRenderer = new ESRI.ArcGIS.Carto.UniqueValueRendererClass() as IUniqueValueRenderer;

//...   set some parameters on the uvRenderer

//the error occurs on this line. SampleAreaFeature.get_Value(idIndex) returns the non-null integer 324
//SampleAreaFeature is a feature in the geodatabase
uvRenderer.AddValue(SampleAreaFeature.get_Value(1).ToString(), "SampleField", redSymbol as ISymbol);

The definition for AddValue is AddValue(string,string,ISymbol). This compiles without an error or warning and AddValue can be found via Reflection at runtime.

I was able to get around this issue by changing my last line to the following:

string value = SampleAreaFeature.get_Value(idIndex).ToString();
uvRenderer.AddValue(value, "SampleField", redSymbol as ISymbol);

Even stranger the following does NOT work:

var value = SampleAreaFeature.get_Value(idIndex).ToString();
uvRenderer.AddValue(value, "SampleField", redSymbol as ISymbol);

And this brings me to the issue at hand. Why are these three snippets compiling differently?

A value that is not explicitly typed is producing different results, both of which compile, but one of which cannot bind to the AddValue method at runtime.

Here is the IL for when the 'var' keyword is used:

IL_00bb: call class ESRI.ArcGIS.Geodatabase.IFeature DataReviewModule.ModGlobals::get_SampleAreaFeature()
IL_00c0: ldloc.2
IL_00c1: callvirt instance object ESRI.ArcGIS.Geodatabase.IFeature::get_Value(int32)
IL_00c6: callvirt instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, object>::Invoke(!0, !1)
IL_00cb: stloc.3
IL_00cc: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`5<class [System.Core]System.Runtime.CompilerServices.CallSite, class [ESRI.ArcGIS.Carto]ESRI.ArcGIS.Carto.IUniqueValueRenderer, object, string, class ESRI.ArcGIS.Display.ISymbol>> DataReviewModule.docWinReview/'<>o__127'::'<>p__1'
IL_00d1: brtrue.s IL_0125

IL_00d3: ldc.i4 256
IL_00d8: ldstr "AddValue"
IL_00dd: ldnull
IL_00de: ldtoken DataReviewModule.docWinReview
IL_00e3: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_00e8: ldc.i4.4
IL_00e9: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_00ee: dup
IL_00ef: ldc.i4.0
IL_00f0: ldc.i4.1
IL_00f1: ldnull
IL_00f2: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
IL_00f7: stelem.ref

And here is the IL for when the 'string' keyword is used:

IL_00f5: call class ESRI.ArcGIS.Geodatabase.IFeature DataReviewModule.ModGlobals::get_SampleAreaFeature()
IL_00fa: ldloc.2
IL_00fb: callvirt instance object ESRI.ArcGIS.Geodatabase.IFeature::get_Value(int32)
IL_0100: callvirt instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, object>::Invoke(!0, !1)
IL_0105: callvirt instance !2 class [mscorlib]System.Func`3<class [System.Core]System.Runtime.CompilerServices.CallSite, object, string>::Invoke(!0, !1)
IL_010a: stloc.3
IL_010b: ldloc.1
IL_010c: ldloc.3
IL_010d: ldsfld string DataReviewModule.ModGlobals::sampleIdField
IL_0112: ldarg.0
IL_0113: ldfld class ESRI.ArcGIS.Display.ISimpleFillSymbol DataReviewModule.docWinReview::redSymbol
IL_0118: isinst ESRI.ArcGIS.Display.ISymbol
IL_011d: callvirt instance void [ESRI.ArcGIS.Carto]ESRI.ArcGIS.Carto.IUniqueValueRenderer::AddValue(string, string, class ESRI.ArcGIS.Display.ISymbol)
recursive
  • 83,943
  • 34
  • 151
  • 241
danielm
  • 339
  • 1
  • 8
  • Possible duplicate of [C# 'var' keyword versus explicitly defined variables](https://stackoverflow.com/questions/429446/c-sharp-var-keyword-versus-explicitly-defined-variables) – Kenneth K. Apr 10 '18 at 20:30
  • 1
    This is not a duplicate of that question. The conclusion of that question is that there is no meaningful difference difference between implicit and explicit typing after compilation. Whereas here I am getting different runtime behavior when using var as opposed to an explicit type. – danielm Apr 10 '18 at 21:19
  • If `get_Value()` returns a `dynamic`, or a type that shadows `.ToString()`, the compilation would be different. That type would have to have an implicit conversion to string defined, but it's possible. If you're using VS, hover over `var`. What type is reported? – recursive Apr 10 '18 at 23:49
  • The existence of `Microsoft.CSharp.RuntimeBinder` lends support to the `dynamic` possibility. – recursive Apr 10 '18 at 23:55
  • @recursive is correct dynamic is shadowing ToString() causing var to be dynamic. Any idea why RuntimeBinder would fail? I can normally pass dynamic objects into strongly typed functions. I would guess this has to do with a failure to bind to the underlying COM object. – danielm Apr 11 '18 at 15:15
  • @danielm: Are there any other overloads to `AddValue()`? – recursive Apr 11 '18 at 15:52
  • @recursive There are not. – danielm Apr 11 '18 at 16:52

1 Answers1

1

SampleAreaFeature.get_Value(idIndex) returned a dynamic.

This results in var value being of type dynamic.

There's some rule about COM binding that I do not understand that's causing two definitions of AddValue to fight here resulting in no dynamic resolution. COM name resolution mashes all the interfaces together for name lookup which has gotten me out of trouble but seems like it's getting you into trouble.

It's also entirely possible that this class doesn't support runtime dyanmic invocation. Reflection runs on the apparent type while dynamic invocation runs on the real type. If these don't match strange things happen and I've got code that depends on them differing to work around a COM interface Guid that got duplicated (one user is MS-Word and I don't know who the other one is). If the real type were to not support dynamic resolution this would be the expected error.

Personally I'd recommend fixing this by ((object)SampleAreaFeature.get_Value(idIndex)).ToString() thus forcing .ToString() to resolve directly rather than via dynamic.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • This could be it. But `null` doesn't prevent resolution unless there are other competing matches. – recursive Apr 11 '18 at 15:56
  • 1
    There are two matches; one on the COM class and one on the COM interface. There's no such thing as explicit implementation in COM. – Joshua Apr 11 '18 at 15:59
  • .ToString() does not return null. get_value() returns the integer 347 and ToString() returns "347". – danielm Apr 11 '18 at 16:49