System.Collections.Generic.List<T>.ToArray()和System.Linq.Enumerable.ToArray<T>()之间的区别是什么?

3
我最近遇到了一个使用特定Web方法的问题:
void CheckGfiHelpdesks(string ticket, GfiCheck[] newHelpdeskChecks, GfiCheck[] otherChecks)
我一直在使用以下代码调用该方法:
List newFailedChecks = new List();
List otherFailedChecks = new List();
//做一些工作,创建新的GfiCheck项,填充列表
Webclient.CheckGfiHelpdesks(Ticket, newFailedChecks.ToArray(), otherFailedChecks.ToArray());
newFailedChecks和otherFailedChecks都是List。当该方法在IIS上作为SOAP服务运行时,这一直运行得很好。
然而,在我将完全相同的方法复制到WCF服务中后,调用产生了“400 bad request”异常。
最终,我发现.ToArray()确实是问题所在。这个:
Webclient.CheckGfiHelpdesks(Ticket, newFailedChecks.ToArray(), otherFailedChecks.ToArray());
即使用System.Linq.Enumerable.ToArray()而不是System.Collections.Generic.List.ToArray()最终解决了问题,异常消失了。
这种差异的解释是什么?数组就是数组,但显然不是?
确切的异常是:
System.ServiceModel.ProtocolException 远程服务器返回了意外的响应:(400)错误请求。
堆栈跟踪:
服务器堆栈跟踪: 在System.ServiceModel.Channels.HttpChannelUtilities.ValidateRequestReplyResponse(HttpWebRequest request, HttpWebResponse response, HttpChannelFactory factory, WebException responseException, ChannelBinding channelBinding) 在System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout) 在System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout) 在System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout) 在System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout) 在System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

异常在[0]处重新抛出:

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

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

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

在MonitoringService.BL.CentronService.CentronServiceClient.CheckGfiHelpdesks(String ticket, GfiCheck[] newHelpdeskChecks, GfiCheck[] otherChecks)位于C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.BL\Service References\CentronService\Reference.cs的第5368行。

在MonitoringService.BL.ConnectorBL.CheckHelpdesks(List`1 clients)位于C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.BL\ConnectorBL.cs的第120行。

在MonitoringService.WinForm.MainForm.LoadChecks()位于C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\MainForm.cs的第124行。

在MonitoringService.WinForm.MainForm.btnLoad_Click(Object sender, EventArgs e)位于C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\MainForm.cs的第114行。

在System.Windows.Forms.Control.OnClick(EventArgs e)处

在DevExpress.XtraEditors.BaseButton.OnClick(EventArgs e)处

在DevExpress.XtraEditors.BaseButton.OnMouseUp(MouseEventArgs e)处

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

在System.Windows.Forms.Control.WndProc(Message& m)处

在DevExpress.Utils.Controls.ControlBase.WndProc(Message& m)处

在DevExpress.XtraEditors.BaseControl.WndProc(Message& msg)处

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

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

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

在System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)处

在System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)处

在System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)处

在System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)处

在System.Windows.Forms.Application.Run(Form mainForm)处

在MonitoringService.WinForm.Program.Main()位于C:\Users\sohrm\documents\visual studio 2010\Projects\MonitoringService\MonitoringService.Client\Program.cs的第22行。

在System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)处

在System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)处

在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()处

在System.Threading.ThreadHelper.ThreadStart_Context(Object state)处

在System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)处

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

在System.Threading.ThreadHelper.ThreadStart()处


我认为你有些困惑了,System.Linq.ToArray<T>()System.Collections.Generic.ToArray()都是不存在的。 - svick
另外,您能否发布服务的完整异常消息和堆栈跟踪? - svick
@RowlandShaw 是的,我确实编辑了这个问题。 - Hackworth
1个回答

1

System.Collections.Generic.List<T>.ToArray()System.Linq.Enumerable.ToArray<T>()之间不应该有任何区别。让我们看看内部发生了什么:

System.Collections.Generic.List<T>只是创建一个新的数组,并将内部项数组复制到其中:

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

System.Linq.Enumerable无法访问列表的内部项数组,因此它通过缓冲区创建数组:

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();
}

缓冲区内发生了什么?List<T>是一个ICollection<T>,因此它只调用List<T>CopyTo实现:

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;
}

正如您所看到的,通过列表的方法CopyTo将项目复制到新数组中,该方法与ToArray方法内部执行的操作完全相同。但是使用System.Linq.Enumerable存在一个小缺点-在将列表项复制到缓冲区后,会创建另一个数组,并将缓冲区中的项目复制到该数组中:

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;
}

所以,在这两种情况下,列表中的项目都是通过相同的方法Array.Copy复制到新数组中的。但在Enumerable的情况下,这会发生两次。如果我处理List,我宁愿选择列表的ToArray实现。

顺便提一下,请检查您的服务配置。错误的可能原因是最大消息大小的设置:https://dev59.com/snRA5IYBdhLWcg3w6SRH - Sergey Berezovskiy
感谢您在这个答案中所做的所有工作,但事实仍然存在:当我传递一个ToArray()给WCF服务时,它会抛出异常,而使用ToArray<T>则不会。因此,说“不应该有区别”并不是很有帮助,因为显然存在问题。也许问题出在WCF上,但肯定存在某个问题。 - Hackworth
我已经检查过了。异常发生在任何最大消息大小,并且我已经测试了每个数组中的1个项目。每个GfiCheck项目都是一些整数和小字符串,如果那样的话,只有几KB。 - Hackworth
新的失败检查列表是什么类型? - Sergey Berezovskiy
两个列表都是List<GfiCheck>类型,填充了GfiCheck项。没有涉及到继承类型,每个项都是相同的类型。已经相应地编辑了问题。 - Hackworth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接