【发布时间】:2019-08-16 16:15:22
【问题描述】:
考虑以下实现 Win32 函数的人为设计的最小 LINQPad 示例CertEnumSystemStoreLocation。该概念扩展到 crypt32.dll 中的其他方法,但这是最简单的演示:
void Main()
{
GetCertificateStoreLocations().Dump();
}
public static IEnumerable<string> GetCertificateStoreLocations()
{
var list = new List<string>();
NativeMethods.CertEnumSystemStoreLocationCallback locationCallback = (location, flags, reserved, state) =>
{
var name = Marshal.PtrToStringUni(location);
list.Add(name);
return true;
};
if (!NativeMethods.CertEnumSystemStoreLocation(0u, IntPtr.Zero, locationCallback))
throw new CryptographicException(Marshal.GetHRForLastWin32Error());
return list.AsReadOnly();
}
private static class NativeMethods
{
/// <seealso href="https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumsystemstorelocation"/>
[DllImport("crypt32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CertEnumSystemStoreLocation(uint reserved,
IntPtr stateObject,
CertEnumSystemStoreLocationCallback callback);
/// <remarks>Implements PFN_CERT_ENUM_SYSTEM_STORE_LOCATION callback function</remarks>
/// <seealso href="https://docs.microsoft.com/en-gb/windows/win32/api/wincrypt/nc-wincrypt-pfn_cert_enum_system_store_location"/>
public delegate bool CertEnumSystemStoreLocationCallback(IntPtr storeLocation,
uint flags,
IntPtr reserved,
IntPtr stateObject);
}
此 Win32 函数的方法是为您枚举证书位置,并为其找到的每个对象运行用户提供的回调函数。它在语义上类似于我经常在跨项目的 Utils.cs 文件中看到的 static void ForEach(this IEnumerable<T>, Action<T>) 扩展名。
在我上面的简单实现中,我使用基于 lambda 的回调作为 List<string> 的闭包来收集 Win32 函数传递给我的 lambda 的值,然后返回整个列表。这满足了我的公共方法的 IEnumerable<string> 签名,但不是特别迭代。
我想知道是否有任何合理的 C# 方法来取消整个 List<string> 创建/填充/返回,以支持适当的迭代器,用于枚举对象的数量并非微不足道的情况。
一个天真的尝试可能是从 lambda 内部使用 yield return,而不是 list.Add,但据我所知,yield return 不能从 lambda 内部使用。
我确实注意到 Win32 方法接受一个指向状态对象的指针,它实际上是您定义、初始化和编组的 struct,Win32 方法会将其传递给您的回调。这可能是可用的,但感觉就像它正在为IAsyncResult 风格的编程和管理WaitHandle 设置我,我希望使用异步编程来解决这个特殊问题只是去给我两个问题而不是一个。
所以我问有没有更好的方法来构造示例代码,可能允许更传统的迭代方法? 即更接近满足IEnumerable/yield return 语义的方法。 p>
(请注意,我不是在寻找传递Func<T>、Action<T> 或委托给进行迭代的方法的方法——这与 crypt32.dll 选择的方法相同设计师。)
【问题讨论】:
-
yield 调用者想一一拉。 API 想一一推送。除了构建一个复杂的多线程邮箱/队列系统之外,我看不出如何调和这两者,在这种情况下这似乎有点矫枉过正。
-
@SimonMourier;这也是我所知道的,这就是为什么我认为我会向世界开放它,看看我是否遗漏了什么......