{"record":{"id":"4777e7dbcab6e487","repo":"JeffreySu/WeiXinMPSDK","slug":"streamfactory-null","errorCode":null,"errorMessage":"streamFactory 不能返回 null。","messagePattern":"streamFactory 不能返回 null。","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs","lineNumber":918,"sourceCode":"        {\n            if (streamFactory == null)\n            {\n                throw new ArgumentNullException(nameof(streamFactory));\n            }\n\n            try\n            {\n                var url = BasePayApis.GetPayApiUrl($\"{Senparc.Weixin.Config.TenPayV3Host}/{{0}}v3/bill/sub-merchant-fundflowbill{UrlQueryHelper.ToParams(data)}\");\n                TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting);\n                var result = await tenPayApiRequest.RequestAsync<SubmerchantBillReturnJson>(url, null, cancellationToken, timeOut, ApiRequestMethod.GET).ConfigureAwait(false);\n\n                //下载资金账单\n                if (result.VerifySignSuccess == true)\n                {\n                    foreach (var item in result.download_bill_list ?? new List<SubmerchantBillDownloadItem>())\n                    {\n                        cancellationToken.ThrowIfCancellationRequested();\n                        var fileStream = streamFactory(item) ?? throw new InvalidOperationException(\"streamFactory 不能返回 null。\");\n                        if (!fileStream.CanWrite)\n                        {\n                            throw new ArgumentException(\"streamFactory 返回的流必须可写。\", nameof(streamFactory));\n                        }\n                        var fileVerify = await TenPayDownloadHelper.DownloadAndVerifyAsync(\n                            tenPayApiRequest, item.download_url, fileStream, item.hash_type, item.hash_value,\n                            timeOut, cancellationToken).ConfigureAwait(false);\n                        if (!fileVerify)\n                        {\n                            result.VerifySignSuccess = false;\n                            result.ResultCode.Additional += \"请求成功，但文件校验错误。请查看日志！\";\n                            SenparcTrace.BaseExceptionLog(new TenpayApiRequestException($\"SubmerchantFundflowBillQueryAsync 下载文件成功，但流式校验失败，正确值：{item.hash_value}\"));\n                        }\n                    }\n                }\n\n                return result;\n            }","sourceCodeStart":900,"sourceCodeEnd":936,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs#L900-L936","documentation":"When downloading merchant fund bills, the library calls the caller-provided streamFactory delegate to obtain a writable Stream for each bill file. If the delegate returns null, an InvalidOperationException is thrown because the download cannot proceed without a destination stream.","triggerScenarios":"Calling a fund-bill download API (DownloadSubmerchantFundFlowBill style) with a streamFactory lambda that can return null, e.g. returning null for certain items or a factory built on File.OpenRead-style logic that fails silently.","commonSituations":"Factories that look up an open stream from a cache/dictionary and miss; returning null for filtered items; reusing a disposed stream abstraction that reports null.","solutions":["Make streamFactory always return a valid writable Stream (e.g. File.Create / new MemoryStream).","If an item should be skipped, filter download_bill_list items before downloading rather than returning null.","Check the factory for early-return paths that yield null and replace with stream creation or an explicit exception."],"exampleFix":"// before\nStream streamFactory(SubmerchantBillDownloadItem item) => cache[item.download_url]; // may be null\n// after\nStream streamFactory(SubmerchantBillDownloadItem item) =>\n    cache.TryGetValue(item.download_url, out var s) && s.CanWrite ? s : File.Create(tempPath(item));","handlingStrategy":"validation","validationCode":"var stream = streamFactory(item);\nif (stream == null) throw new InvalidOperationException(\"streamFactory must return a writable stream.\");","typeGuard":"bool factoryValid = streamFactory(item) is Stream;","tryCatchPattern":"try { await apis.DownloadFundFlowBillAsync(..., streamFactory); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"streamFactory\")) { logger.Error(ex, \"streamFactory returned null\"); throw; }","preventionTips":["Never return null from a stream factory; throw a meaningful exception instead","Test the factory with real items before wiring it in","Prefer File.Create or MemoryStream as safe defaults"],"tags":["argument-validation","null-check","stream","file-download"],"backgroundTag":"null-argument","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}