Create dynamic custom selector - filehelpers

I have MultiRecordEngine with a CustomSelector in which I'm trying to loop through many lines starting with different strings
Instead of a long list of 'ifs' like this (which works fine):
if (recordLine.Length == 0)
return null;
if (recordLine.StartsWith("EDI_DC40_U"))
return typeof(IDOC_EDI_DC40_U);
if (recordLine.StartsWith("E2EDL22"))
return typeof(IDOC_E2EDL22);
[...(some more ifs)...]
else
return null;
I wanted to try something more fancy. Having a class with the line prefix in its name for each kind of possible lines, I wanted to do this:
if (recordLine.Length == 0)
return null;
foreach(Type type in GetIDOCTypes()) {
// remove class prefix (IDOC_) from type name
string name = type.Name.Substring(6,type.Name.Length - 5);
if (recordLine.StartsWith(name))
return type;
}
return null
{"You must call BeginRead before use the engine in a foreach loop."} System.Exception {FileHelpers.FileHelpersException}
When I do this I get above Filehelper exception and I just don't get how, when and where I should call the 'BeginRead'...
Is my approach completely wrong?
Here is my sample project.
Thanks!

Have you got a sample project I can look at ? There is certainly nothing to prevent you from doing this specifically unless you are accessing the engines record array but I suspect that you actually returning null rather than a default type thus it doesn't know what type of record you have.
BeginRead must be called in order to start parsing the file in an asynchronous fashion. You haven't actually posted your code to show how you are reading the data. I'm on my phone right now but you must have something like:
var engine = new MultiRecordEngine(typeof (Orders),
typeof (Customer),
typeof (SampleType));
engine.RecordSelector = new RecordTypeSelector(CustomSelector);
var res = engine.ReadFile("Input.txt");
At which point the engine will start to loop through the records in a synchronous pattern calling your custom selector as it goes.
Diagnosis
Error handling
So I took a look at the sample project and it would appear that I failed to make sure to ask you whether you'd checked the inner exceptions. The full error you get from the test project is:
System.Exception was unhandled
HResult=-2146233088
Message=Selector failed to process correctly
Source=FileHelpers
StackTrace:
at FileHelpers.MultiRecordEngine.ReadStream(IRecordReader reader)
at FileHelpers.MultiRecordEngine.ReadFile(String fileName)
at FileHelpersLoopTest.IDOCFileClass.readIDOCFile()
at FileHelpersLoopTest.Program.Main(String[] args)
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
HResult=-2146233086
Message=Index and length must refer to a location within the string.
Parameter name: length
ParamName=length
Source=mscorlib
StackTrace:
at System.String.Substring(Int32 startIndex, Int32 length)
at FileHelpersLoopTest.IDOCFileClass.CustomSelector(MultiRecordEngine engine, String recordLine)
at FileHelpers.MultiRecordEngine.ReadStream(IRecordReader reader)
InnerException:
This should have immediately given you a hint from the inner exception as the stack trace says it failed with "Index and length must refer to a location within the string. Parameter name: length" inside System.String.Substring called from CustomSelector showing that it had indeed reached your code but you were using some incorrect indexing namely the length of the string.
Also, I noticed that you are checking the length of RecordLine but not whether it is null, so another error could occur there. I would advise using System.String.IsNullOrWhiteSpace inside of just checking the length to be safe.
Working code
The following works fine for me, but you still have work to do on matching your IODC types to the EDI types that were in the file. I tried to add the EDI_ prefix but the types were still a mismatch so I'll leave that part up to you as I'm assuming this is just down to it being sample data or a naming problem.
// Failing code block
if (string.IsNullOrWhiteSpace(recordLine))
return null;
var types = GetIDOCTypes();
foreach (Type type in types)
{
// remove class prefix (IDOC_) from type name
string name = "EDI_" + type.Name.Substring(6, type.Name.Length - 5);
Console.WriteLine($"{name} vs {recordLine.Substring(0, name.Length)}");
if (recordLine.StartsWith(name))
return type;
}
return null;
// END Failing code block

Related

Sitecore 7 ContentSearch crawling failure: "Crawler : AddRecursive DoItemAdd failed"

When we try to rebuild our Lucene (ContentSearch) indexes, our CrawlingLog is filled up with these exceptions:
7052 15:08:21 WARN Crawler : AddRecursive DoItemAdd failed - {5A1E50E4-46B9-42D5-B743-1ED10D15D47E}
Exception: System.AggregateException
Message: One or more errors occurred.
Source: mscorlib
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
at System.Threading.Tasks.Parallel.PartitionerForEachWorker[TSource,TLocal](Partitioner`1 source, ParallelOptions parallelOptions, Action`1 simpleBody, Action`2 bodyWithState, Action`3 bodyWithStateAndIndex, Func`4 bodyWithStateAndLocal, Func`5 bodyWithEverything, Func`1 localInit, Action`1 localFinally)
at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IEnumerable`1 source, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Action`3 bodyWithStateAndIndex, Func`4 bodyWithStateAndLocal, Func`5 bodyWithEverything, Func`1 localInit, Action`1 localFinally)
at System.Threading.Tasks.Parallel.ForEach[TSource](IEnumerable`1 source, ParallelOptions parallelOptions, Action`1 body)
at Sitecore.ContentSearch.AbstractDocumentBuilder`1.AddItemFields()
at Sitecore.ContentSearch.LuceneProvider.CrawlerLuceneIndexOperations.GetIndexData(IIndexable indexable, IProviderUpdateContext context)
at Sitecore.ContentSearch.LuceneProvider.CrawlerLuceneIndexOperations.BuildDataToIndex(IProviderUpdateContext context, IIndexable version)
at Sitecore.ContentSearch.LuceneProvider.CrawlerLuceneIndexOperations.Add(IIndexable indexable, IProviderUpdateContext context, ProviderIndexConfiguration indexConfiguration)
at Sitecore.ContentSearch.SitecoreItemCrawler.DoAdd(IProviderUpdateContext context, SitecoreIndexableItem indexable)
at Sitecore.ContentSearch.HierarchicalDataCrawler`1.CrawlItem(Tuple`3 tuple)
Nested Exception
Exception: System.ArgumentOutOfRangeException
Message: Index and length must refer to a location within the string.
Parameter name: length
Source: mscorlib
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean fAlwaysCopy)
at Sitecore.Data.ShortID.Encode(String guid)
at Sitecore.ContentSearch.FieldReaders.MultiListFieldReader.GetFieldValue(IIndexableDataField indexableField)
at Sitecore.ContentSearch.FieldReaders.FieldReaderMap.GetFieldValue(IIndexableDataField field)
at Sitecore.ContentSearch.LuceneProvider.LuceneDocumentBuilder.AddField(IIndexableDataField field)
at System.Threading.Tasks.Parallel.<>c__DisplayClass32`2.<PartitionerForEachWorker>b__30()
at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
at System.Threading.Tasks.Task.<>c__DisplayClass11.<ExecuteSelfReplicating>b__10(Object param0)
This appears to be caused by the ShortID.Encode(string) method expecting the GUID in the string parameter to have brackets (" { " and " } ") around it. Some of our multilist field relationships were associated programmatically using Guid.ToString(), which does not include the brackets. Unfortunately, these values cause the ShortID.Encode() method to choke.
First things first: find all the places you call MultiListField.Add(string) and change Guid.ToString() to Guid.ToString("B"). This will resolve the issue for all new relationships.
Create a custom FieldReader class to replace the standard MultiListFieldReader (we called ours CustomMultiListFieldReader).
Set your custom class to inherit from Sitecore.ContentSearch.FieldReaders.FieldReader.
Decompile the Sitecore.ContentSearch.FieldReaders.MultiListFieldReader.GetFieldValue(IIndexableDataField) method into your custom class.
Before the if (ID.IsID(id)) line, add the following code:
if (!str.StartsWith("{") && !str.EndsWith("}"))
id = String.Format("{{{0}}}", str);
In your index configuration (we added ours to the default, Sitecore.ContentSearch.DefaultIndexConfiguration.config) change the fieldReaderType for the MultiList fields to your custom type. (This can be found in your config at sitecore/contentSearch/configuration/defaultIndexConfiguration/fieldReaders/mapFieldByTypeName/fieldReader.)
Full disclosure: I don't love this approach because if the default implementation of the MultiListFieldReader ever changed, we'd be without those changes. But this allows the items to be included in the index without reformatting all of the GUIDs in every multilist field.

What things should I consider when I get an intermittent exception in Visual Basic .net (debugging in Visual Studio Express 2010)?

I'm testing my Visual Basic .net application by running it in Visual Studio express.
Sometimes it runs OK, but other times I get an exception (details below, if useful). I'm not sure why I get the exception as the particular part of the application that I am testing re-uses code that works OK for other features.
What should I check, what would give me a hint as to the root cause.
Things I tried so far are:
Rewriting some of the code to be more robust. The outcome of this was that this approach was getting out of control - I was correcting everything. I made changes such asing alternative libraries (e.g. replacing CInt with convertTo etc). Some lazy declarations, but it occurred to me that there was no problem with these with the code before my changes. And every change I seemed to solve uncovered yet another problem, another different exception
So I thought something must be fundamentally wrong with my installation, and a search found discussion group posts from people experiencing something similar. The suggested remedy would be to re-install the .net framework. So I did that and the problems still occured.
Any thoughts on approach to get to the root of the problem? I'm not asking for a solution but some fresh ideas would be very welcome.
Update:
I've added in the following code to get more detail (+1 thanks #Meta-Knight)
Dim exceptionMessage As String = ex.Message
Console.WriteLine("Message: \n" & exceptionMessage)
Dim exceptionTargetSite As String = ex.TargetSite.ToString
Console.WriteLine("Inner: \n" & ex.TargetSite.ToString)
Dim exceptionSource As String = ex.Source
Console.WriteLine("Source\n:" & exceptionSource)
' put the stack trace into a variable
' (this helps debugging - put a break point after this is assigned
' to see the contents)
Dim stackTrace As String = ex.StackTrace.ToString()
Console.WriteLine("Stack trace\n:" & stackTrace)
More details about exception
Error - Exception Message
"Arithmetic operation resulted in an overflow."
Exception Target Site:
"Int32 ToInteger(System.String)"
Exception Source:
"Microsoft.VisualBasic"
at Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value) at MyCo_3rd-Party-Industrial-Printing-System.Customer_EanUpc_serialNumberGeneratorAssembly.btnPrint_Click(Object sender, EventArgs e) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\PrintForms\Customer_EanUpc_serialNumberGeneratorAssembly.vb:line 271 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.PerformClick() at System.Windows.Forms.Form.ProcessDialogKey(Keys keyData) at System.Windows.Forms.Control.ProcessDialogKey(Keys keyData) at System.Windows.Forms.Control.PreProcessMessage(Message& msg) at System.Windows.Forms.Control.PreProcessControlMessageInternal(Control target, Message& msg) at System.Windows.Forms.Application.ThreadContext.PreTranslateMessage(MSG& msg) at System.Windows.Forms.Application.ThreadContext.System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FPreTranslateMessage(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.RunDialog(Form form) at System.Windows.Forms.Form.ShowDialog(IWin32Window owner) at System.Windows.Forms.Form.ShowDialog() at MyCo_3rd-Party-Industrial-Printing-System.utlForm.openNewModalForm(String form) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\Utilities\utlForm.vb:line 128
Update 2:
The code around line 272 was:
For i As Integer = 1 To CInt(numQuantity)
If generationMethods() Then
The code around line 272 is now (I've adjusted it):
Dim numQuantityAsStr As String = numQuantity.Text
Dim numQuantityAsInt As Integer = Convert.ToInt32(numQuantityAsStr)
For i As Integer = 1 To numQuantityAsInt
If generationMethods() Then
numQuantity is a String variable for a field in the Windows form I am using and this has a value put in it by the user, the field is used to specify the quantity of something so this variable is converted to a integer so that it can be used in a for loop. The test value I am using is always entering 1 in this field, and sometimes this causes the exception.
My alteration uses what I think is a more modern type conversion Convert.ToInt32 rather than CInt I've also introduced intermediate steps to help debug.
The exception now does not occur here but I did try this the other week and another separate exception popped up (if I remember) from somewhere else so we'll see if this fixes it. Any thoughts as to why using a different conversion utility would solve the problem?
Update 3:
An Exception now occurs elsewhere:
(But why?! This one is thrown from library code!)
The following code caused it:
Dim dateNowAsStr As String = DateTime.Now.Date.ToString
Exception Message:
"Value to add was out of range. Parameter name: value"
Exception target site:
"System.DateTime Add(Double, Int32)"
Exception source:
"mscorlib"
" at System.DateTime.Add(Double value, Int32 scale) at System.TimeZoneInfo.TransitionTimeToDateTime(Int32 year, TransitionTime transitionTime) at System.TimeZoneInfo.GetDaylightTime(Int32 year, AdjustmentRule rule) at System.TimeZoneInfo.GetIsDaylightSavingsFromUtc(DateTime time, Int32 Year, TimeSpan utc, AdjustmentRule rule, Boolean& isAmbiguousLocalDst) at System.TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(DateTime time, Boolean& isAmbiguousLocalDst) at System.DateTime.get_Now() at GenerationLibrary.GenerationUtilities.callserialNumberGenerator(String serialNumberGeneratorSnFile, String serialNumberGeneratorRange, DefaultSettingsHandler settings) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\GenerationLibrary\GenerationUtilities.vb:line 28 at GenerationLibrary.MyCoSnGeneration.constructMyCoSn(DataRow& oDataRow, DefaultSettingsHandler& settings) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\GenerationLibrary\MyCoSnGeneration.vb:line 18 at MyCo_3rd-Party-Industrial-Printing-System.Customer_EanUpc_serialNumberGeneratorAssembly.generationMethods() in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\PrintForms\Customer_EanUpc_serialNumberGeneratorAssembly.vb:line 40 at MyCo_3rd-Party-Industrial-Printing-System.Customer_EanUpc_serialNumberGeneratorAssembly.btnPrint_Click(Object sender, EventArgs e) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\PrintForms\Customer_EanUpc_serialNumberGeneratorAssembly.vb:line 275 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.PerformClick() at System.Windows.Forms.Form.ProcessDialogKey(Keys keyData) at System.Windows.Forms.Control.ProcessDialogKey(Keys keyData) at System.Windows.Forms.Control.PreProcessMessage(Message& msg) at System.Windows.Forms.Control.PreProcessControlMessageInternal(Control target, Message& msg) at System.Windows.Forms.Application.ThreadContext.PreTranslateMessage(MSG& msg) at System.Windows.Forms.Application.ThreadContext.System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FPreTranslateMessage(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.RunDialog(Form form) at System.Windows.Forms.Form.ShowDialog(IWin32Window owner) at System.Windows.Forms.Form.ShowDialog() at MyCo_3rd-Party-Industrial-Printing-System.utlForm.openNewModalForm(String form) in C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\Utilities\utlForm.vb:line 128"
Update 4
BUT
Dim dateNowAsStr As String = CStr(DateTime.Now)
does not cause the exception
Before rewriting or reinstalling anything, you should try to identify the source and the reason for the error.
The first thing to do is to analyse the error message and stack trace. If you include debugging files (.pdb) with your application's files, you will get more detailed information such as the line number which can be helpful.
If that doesn't give enough information about the circumstances of the error, then you can add some logging to your app. By logging what the user does in your app it might help reproduce the problem.
Finally you can always get help by searching on google or asking on StackOverflow, but make sure to include the actual error message, not just the stack trace... You should also post the code where the error happens.
Edit:
So the actual error is: "Arithmetic operation resulted in an overflow."
Googling this would have helped: you would have found out that such an error happens when you're trying to convert a number which is out of bounds for an integer. If you expect to have some very large numbers you can try converting to Long instead.
I think you can start by looking at what is at line 271 of the Customer_EanUpc_serialNumberGeneratorAssembly.vb source code file.
Which is located: C:\labelprint\MyCo 3rd-Party-Industrial-Printing-System v2\MyCo 3rd-Party-Industrial-Printing-System\PrintForms\ directory.
It looks like the problem is related to a conversion between string to integer where maybe it failed because the string cannot be converted to integer... maybe it has alphanumeric characters...
"Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)"
Did you already check that?
Why you get the error some times and not always could be because (and I'm guessing here) the code in serialNumberGeneratorAssembly sometimes generates numeric only values (that can be correctly converted to integer) and some other times generates alphanumeric serial numbers (that throw the convertion exception).
UPDATE: the code that generates the Serial Numbers is generating numbers bigger than an Integer value. Try converting to Long instead of Integer... or have a look at System.Numerics.BigInteger in case those numbers are too big.
That explains the: "Arithmetic operation resulted in an overflow." exception message.

Updating/Inserting is not supported by data source

I've been making my first ASP.NET Visual Studio website and I have just started working with databases, I've made a table and a backoffice page for me to control the content of my table.
This page follows the following rules:
-> I've added the SQLDataSource and configured.
-> I've added a gridview to see my content and configured it to my SQLDataSource, here I've added the Edit/Delete options.
-> I've added a DetailsView configured to my SWLDataSource, here I've added New option (to create new entries in my table)
My database has the id column set as primary key (data type=int; allow nulls=not checked)
Every time I try to update my data base through this backoffice page I get the following error:
Updating is not supported by data
source 'SqlDataSource1' unless
UpdateCommand is specified.
Description: An unhandled exception
occurred during the execution of the
current web request. Please review the
stack trace for more information about
the error and where it originated in
the code.
Exception Details:
System.NotSupportedException: Updating
is not supported by data source
'SqlDataSource1' unless UpdateCommand
is specified.
Source Error:
An unhandled exception was generated
during the execution of the current
web request. Information regarding the
origin and location of the exception
can be identified using the exception
stack trace below.
Stack Trace:
[NotSupportedException: Updating is
not supported by data source
'SqlDataSource1' unless UpdateCommand
is specified.]
System.Web.UI.WebControls.SqlDataSourceView.ExecuteUpdate(IDictionary
keys, IDictionary values, IDictionary
oldValues) +1644420
System.Web.UI.DataSourceView.Update(IDictionary
keys, IDictionary values, IDictionary
oldValues,
DataSourceViewOperationCallback
callback) +92
System.Web.UI.WebControls.GridView.HandleUpdate(GridViewRow
row, Int32 rowIndex, Boolean
causesValidation) +907
System.Web.UI.WebControls.GridView.HandleEvent(EventArgs
e, Boolean causesValidation, String
validationGroup) +704
System.Web.UI.WebControls.GridView.OnBubbleEvent(Object
source, EventArgs e) +95
System.Web.UI.Control.RaiseBubbleEvent(Object
source, EventArgs args) +37
System.Web.UI.WebControls.GridViewRow.OnBubbleEvent(Object
source, EventArgs e) +123
System.Web.UI.Control.RaiseBubbleEvent(Object
source, EventArgs args) +37
System.Web.UI.WebControls.LinkButton.OnCommand(CommandEventArgs
e) +118
System.Web.UI.WebControls.LinkButton.RaisePostBackEvent(String
eventArgument) +135
System.Web.UI.WebControls.LinkButton.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String
eventArgument) +10
System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler
sourceControl, String eventArgument)
+13 System.Web.UI.Page.RaisePostBackEvent(NameValueCollection
postData) +175
System.Web.UI.Page.ProcessRequestMain(Boolean
includeStagesBeforeAsyncPoint, Boolean
includeStagesAfterAsyncPoint) +1565
I could really use some help here!
The errormessage says it all : 'Updating is not supported by data source 'SqlDataSource1' unless UpdateCommand is specified.'. You have to assign sql code or the name of a stored procedure to the property UpdateCommand so that the SqlDataSource knows how to handle your update.

How do I express a void method call as the result of DynamicMetaObject.BindInvokeMember?

I'm trying to give a short example of IDynamicMetaObjectProvider for the second edition of C# in Depth, and I'm running into issues.
I want to be able to express a void call, and I'm failing. I'm sure it's possible, because if I dynamically call a void method using the reflection binder, all is fine. Here's a short but complete example:
using System;
using System.Dynamic;
using System.Linq.Expressions;
class DynamicDemo : IDynamicMetaObjectProvider
{
public DynamicMetaObject GetMetaObject(Expression expression)
{
return new MetaDemo(expression, this);
}
public void TestMethod(string name)
{
Console.WriteLine(name);
}
}
class MetaDemo : DynamicMetaObject
{
internal MetaDemo(Expression expression, DynamicDemo demo)
: base(expression, BindingRestrictions.Empty, demo)
{
}
public override DynamicMetaObject BindInvokeMember
(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
Expression self = this.Expression;
Expression target = Expression.Call
(Expression.Convert(self, typeof(DynamicDemo)),
typeof(DynamicDemo).GetMethod("TestMethod"),
Expression.Constant(binder.Name));
var restrictions = BindingRestrictions.GetTypeRestriction
(self, typeof(DynamicDemo));
return new DynamicMetaObject(target, restrictions);
}
}
class Test
{
public void Foo()
{
}
static void Main()
{
dynamic x = new Test();
x.Foo(); // Works fine!
x = new DynamicDemo();
x.Foo(); // Throws
}
}
This throws an exception:
Unhandled Exception:
System.InvalidCastException: The
result type 'System.Void' of the
dynamic binding produced by the object
with type 'DynamicDemo' for the binder
'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder'
is not compatible with the result type 'System.Object' expected by the
call site.
If I change the method to return object and return null, it works fine... but I don't want the result to be null, I want it to be void. That works fine for the reflection binder (see the first call in Main) but it fails for my dynamic object. I want it to work like the reflection binder - it's fine to call the method, so long as you don't try to use the result.
Have I missed a particular kind of expression I can use as the target?
This is similar to:
DLR return type
You do need to match the return type specified by the ReturnType property. For all of the standard binaries this is fixed to object for almost everything or void (for the deletion operations). If you know you're making a void call I'd suggest wrapping it in:
Expression.Block(
call,
Expression.Default(typeof(object))
);
The DLR used to be quite lax about what it would allow and it would provide some minimal amount of coercion automatically. We got rid of that because we didn't want to provide a set of convensions which may or may not have made sense for each language.
It sounds like you want to prevent:
dynamic x = obj.SomeMember();
There's no way to do that, there'll always be a value returned that the user can attempt to continue to interact with dynamically.
I don't like this, but it seems to work; the real problem seems to be the binder.ReturnType coming in oddly (and not being dropped ("pop") automatically), but:
if (target.Type != binder.ReturnType) {
if (target.Type == typeof(void)) {
target = Expression.Block(target, Expression.Default(binder.ReturnType));
} else if (binder.ReturnType == typeof(void)) {
target = Expression.Block(target, Expression.Empty());
} else {
target = Expression.Convert(target, binder.ReturnType);
}
}
return new DynamicMetaObject(target, restrictions);
Perhaps the callsite expects null to be returned but discards the result - This enum looks interesting, particularly the "ResultDiscarded" flag...
[Flags, EditorBrowsable(EditorBrowsableState.Never)]
public enum CSharpBinderFlags
{
BinaryOperationLogical = 8,
CheckedContext = 1,
ConvertArrayIndex = 0x20,
ConvertExplicit = 0x10,
InvokeSimpleName = 2,
InvokeSpecialName = 4,
None = 0,
ResultDiscarded = 0x100,
ResultIndexed = 0x40,
ValueFromCompoundAssignment = 0x80
}
Food for thought...
UPDATE:
More hints can be gleaned from Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView which is used (I presume) as a visualizer for debuggers. The method TryEvalMethodVarArgs examines the delegate and creates a binder with the result discarded flag (???)
Type delegateType = Expression.GetDelegateType(list.ToArray());
if (string.IsNullOrEmpty(name))
{
binder = new CSharpInvokeBinder(CSharpCallFlags.ResultDiscarded, AccessibilityContext, list2.ToArray());
}
else
{
binder = new CSharpInvokeMemberBinder(CSharpCallFlags.ResultDiscarded, name, AccessibilityContext, types, list2.ToArray());
}
CallSite site = CallSite.Create(delegateType, binder);
... I'm at the end of my Reflector-foo here, but the framing of this code seems a bit odd since the TryEvalMethodVarArgs method itself expects an object as a return type, and the final line returns the result of the dynamic invoke. I'm probably barking up the wrong [expression] tree.
-Oisin
The C# binder (in Microsoft.CSharp.dll) knows whether or not the result is used; as x0n (+1) says, it keeps track of it in a flag. Unfortunately, the flag is buried inside a CSharpInvokeMemberBinder instance, which is a private type.
It looks like the C# binding mechanism uses ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (a property on an internal interface) to read it out; CSharpInvokeMemberBinder implements the interface (and property). The job appears to be done in Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). That method has code that throws if the aforementioned ResultDiscarded property doesn't return true if the type of the expression is void.
So it doesn't look to me like there's an easy way to tease out the fact that the result of the expression is dropped from the C# binder, in Beta 2 at least.

Debugging COMException

I am using an ancient Intergraph routing library to do some routing.
I could create several ActiveX and COM objects just fine but there is this one type "Location" and "LocationCtrl" that throws a COMException on creation.
Here's the relevant code
_sourceLocation.Streets = _streets;
_sourceLocation.VerifyAutomatic = fa_sourceLocationse;
_sourceLocation.VerifyType = VerifyTypeConstants.VerifyUsingA_sourceLocation_sourceLocation;
_sourceLocation.ReplaceTextAfterParse = true;
_sourceLocation.ReplaceTextAfterVerify = true;
_sourceLocation.Map = _mapOcx;
The COMException was thrown upon setting the Streets property.
Here's the exception:
System.Runtime.InteropServices.COMException was unhandled
Message="Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))"
Source="mscorlib"
ErrorCode=-2147418113
StackTrace:
at System.RuntimeType.ForwardCallToInvokeMember(String memberName, BindingFlags flags, Object target, Int32[] aWrapperTypes, MessageData& msgData)
at Intergraph.LocationCtrlClass.set_Streets(Object )
at ECNavigator.Routing.<InitializeRouting>b__0(LocationCtrl l) in C:\Users\Chakrit\Projects\NavigatorService\Experimental\Routing.cs:line 92
at ECNavigator.Util.FrameworkExtensions.ForEach[T](IEnumerable`1 enum, Action`1 action) in C:\Users\Chakrit\Projects\NavigatorService\Experimental\Util\FrameworkExtensions.cs:line 18
at ECNavigator.Routing.InitializeRouting() in C:\Users\Chakrit\Projects\NavigatorService\Experimental\Routing.cs:line 90
at ECNavigator.Routing..ctor(String mapFile, LocationCtrl sourceLocation, LocationCtrl destinationLocation) in C:\Users\Chakrit\Projects\NavigatorService\Experimental\Routing.cs:line 62
at ECNavigator.Program.Main() in C:\Users\Chakrit\Projects\NavigatorService\Experimental\Program.cs:line 23
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
The entire application is extremely simple, it contained only code that initialize some of the supporting objects, however this particular Location type would initialize just fine but on first invocation of property getters/setters, it would throw the above exception.
I have been debugging this for a few days already and havn't found a way to instantiate and initialize the Location class properly.
Any clue? Some pointers on how to further investigate this?