3

I've recently had quite a problem with a certain web method I'm using:

void CheckGfiHelpdesks(string ticket, GfiCheck[] newHelpdeskChecks, GfiCheck[] otherChecks)

I've been calling that method with this code:

List<GfiCheck> newFailedChecks = new List<GfiCheck>();

List<GfiCheck> otherFailedChecks = new List<GfiCheck>();

//do some work, create new GfiCheck items, fill the lists

Webclient.CheckGfiHelpdesks(Ticket, newFailedChecks.ToArray(), otherFailedChecks.ToArray());

newFailedChecks and otherFailedChecks are List. This has been working fine when the method was running on IIS as a SOAP service.

However, after I copied the exact same method into a WCF service, the call produced a "400 bad request" exception.

Eventually I figured out that .ToArray() was indeed the problem. This:

Webclient.CheckGfiHelpdesks(Ticket, newFailedChecks.ToArray<GfiCheck>(), otherFailedChecks.ToArray<GfiCheck>());

i.e. using the System.Linq.Enumerable.ToArray<T>() instead of System.Collections.Generic.List<T>.ToArray() finally solved the problem and the exception went away.

What is the explanation for this difference? An Array is an Array, but apparently not?

The exact Exception is:

System.ServiceModel.ProtocolException

The remote server returned an unexpected response: (400) Bad Request.

StackTrace:

Server stack trace:

at System.ServiceModel.Channels.HttpChannelUtilities.ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response, HttpChannelFactory factory, WebException responseException, ChannelBinding channelBinding)

at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)

at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)

at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)

at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)

at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)

at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

>

Exception rethrown at [0]:

at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)

at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)

at MonitoringService.BL.CentronService.ICentronService.CheckGfiHelpdesks(String ticket, GfiCheck[] newHelpdeskChecks, GfiCheck[] otherChecks)

at MonitoringService.BL.CentronService.CentronServiceClient.CheckGfiHelpdesks(String ticket, GfiCheck[] newHelpdeskChecks, GfiCheck[] otherChecks) in C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.BL\Service References\CentronService\Reference.cs:Zeile 5368.

at MonitoringService.BL.ConnectorBL.CheckHelpdesks(List`1 clients) in C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.BL\ConnectorBL.cs:Zeile 120.

at MonitoringService.WinForm.MainForm.LoadChecks() in C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\MainForm.cs:Zeile 124.

at MonitoringService.WinForm.MainForm.btnLoad_Click(Object sender, EventArgs e) in C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\MainForm.cs:Zeile 114.

at System.Windows.Forms.Control.OnClick(EventArgs e)

at DevExpress.XtraEditors.BaseButton.OnClick(EventArgs e)

at DevExpress.XtraEditors.BaseButton.OnMouseUp(MouseEventArgs e)

at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)

at System.Windows.Forms.Control.WndProc(Message& m)

at DevExpress.Utils.Controls.ControlBase.WndProc(Message& m)

at DevExpress.XtraEditors.BaseControl.WndProc(Message& msg)

at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)

at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)

at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(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.Run(Form mainForm)

at MonitoringService.WinForm.Program.Main() in C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\Program.cs:Zeile 22.

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.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ThreadHelper.ThreadStart()

Hackworth
  • 1,069
  • 2
  • 9
  • 23
  • I think you're confused, there is no `System.Linq.ToArray()` or `System.Collections.Generic.ToArray()`. – svick May 08 '12 at 07:40
  • Also, could you post the full exception message and stack trace from the service? – svick May 08 '12 at 07:44
  • 1
    Do you instead mean [System.Linq.Enumerable.ToArray](http://msdn.microsoft.com/en-us/library/bb298736.aspx) and [System.Collections.Generic.List.ToArray](http://msdn.microsoft.com/en-us/library/x303t819.aspx)? – Rowland Shaw May 08 '12 at 07:48
  • @RowlandShaw Yes indeed, I edited the question. – Hackworth May 08 '12 at 07:52

1 Answers1

1

There should not be difference between System.Collections.Generic.List<T>.ToArray() and System.Linq.Enumerable.ToArray<T>(). Lets see what happens inside:

System.Collections.Generic.List<T> just creates new array and copies internal items array to it:

public T[] ToArray()
{
    T[] destinationArray = new T[this._size];
    Array.Copy(this._items, 0, destinationArray, 0, this._size);
    return destinationArray;
}

System.Linq.Enumerable cannot access internal items array of list, so it creates array via buffer:

public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)    
        throw Error.ArgumentNull("source");

    Buffer<TSource> buffer = new Buffer<TSource>(source);
    return buffer.ToArray();
}

What happens inside buffer? List<T> is a ICollection<T> thus it just calls CopyTo implementation of List<T>:

internal Buffer(IEnumerable<TElement> source)
{
   TElement[] array = null;
   ICollection<TElement> is2 = source as ICollection<TElement>;
   length = is2.Count;
   if (length > 0)
   {
       array = new TElement[length];
       // implemented as Array.Copy(this._items, 0, array, 0, this._size);
       is2.CopyTo(array, 0);
   }

   this.items = array;
   this.count = length;
}

As you can see, items are copied to new array via list's method CopyTo, which do exactly same thing as inside ToArray method. But with System.Linq.Enumerable you have small drawback - after list items was copied to buffer, another array is created, and items from buffer copied to that array:

internal TElement[] ToArray()
{
   if (this.count == 0)        
       return new TElement[0];

   if (this.items.Length == this.count)        
       return this.items;

   TElement[] destinationArray = new TElement[this.count];
   Array.Copy(this.items, 0, destinationArray, 0, this.count);
   return destinationArray;
}

So, in both cases items from list copied to new array via same method Array.Copy. But in case of Enumerable this happens twice. I'd rather go with list's ToArray implementation if I deal with List.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • BTW check your service configuration. Possible reason of error is settings for max message size: http://stackoverflow.com/questions/784606/large-wcf-web-service-request-failing-with-400-http-bad-request – Sergey Berezovskiy May 08 '12 at 08:30
  • Thanks for all the work in this answer, but the fact remains: The WCF service gives me an exception when I pass it a ToArray(), whereas it doesn't with ToArray. So saysing "there should not be a difference" is not exactly helpful when clearly there is. Maybe the problem lies in WCF, but there is a problem somewhere. – Hackworth May 08 '12 at 08:32
  • I've checked that. The exception happens at any max message size, and I've tested for 1 item per array. Each GfiCheck item is a small number of ints and small strings, a few kb if that. – Hackworth May 08 '12 at 08:35
  • What exact type of newFailedChecks list? – Sergey Berezovskiy May 08 '12 at 08:38
  • Both lists are List, filled with GfiCheck items. No inherited types involved, every item is of the same type. Edited the question accordingly. – Hackworth May 08 '12 at 08:43