C#: 如何将null传递给期望引用的函数?

51

我有以下函数:

public static extern uint FILES_GetMemoryMapping(
    [MarshalAs(UnmanagedType.LPStr)] string pPathFile,
    out ushort Size,
    [MarshalAs(UnmanagedType.LPStr)] string MapName,
    out ushort PacketSize,
    ref Mapping oMapping,
    out byte PagesPerSector);

我希望这样称呼它:

FILES_GetMemoryMapping(MapFile, out size, MapName,
    out PacketSize, null, out PagePerSector);

很不幸,我无法在需要类型为ref Mapping的字段中传递null,而且我尝试过的任何转换都无法解决这个问题。

有什么建议吗?


可能是如何在C#中处理可选的C++ DLL结构参数的重复问题。 - River
很遗憾,C#不能像VB.net那样处理可选引用。在VB.net中,它会在后台创建虚拟变量,并允许您省略参数。 - Brain2000
9个回答

49
你不能传递 null 的原因是因为 C# 编译器对 ref 参数进行了特殊处理。任何 ref 参数必须是一个引用,可以被传递到你正在调用的函数中。由于你想传递 null,编译器会拒绝这样做,因为你没有提供函数期望得到的引用。
你唯一的真正选项就是创建一个本地变量,将它设置为 null,然后将其传入。编译器不允许你做更多的事情。

1
这是唯一一个直接切入核心问题的答案。 - Max Barraclough
有没有办法使用DateTime来实现这个?我需要传递一个不存在的DT值。 - Tomas Beblar
1
@TomasBeblar,DateTime? 呢?也就是可选类型? - Bolpat

34

我假设Mapping是一个结构体?如果是这样,您可以在两个版本的FILES_GetMemoryMapping()原型中使用不同的签名。对于第二个重载,在您想要传递null时,将参数设置为IntPtr,并使用IntPtr.Zero

public static extern uint FILES_GetMemoryMapping(
    [MarshalAs(UnmanagedType.LPStr)] string pPathFile,
    out ushort Size,
    [MarshalAs(UnmanagedType.LPStr)] string MapName,
    out ushort PacketSize,
    IntPtr oMapping,
    out byte PagesPerSector);

调用示例:

FILES_GetMemoryMapping(MapFile, out size, MapName,
   out PacketSize, IntPtr.Zero, out PagePerSector);
如果 Mapping 实际上是一个类而不是一个结构体,只需在传递它之前将其值设置为 null。

5
如果有一个包含8个指向结构体的指针的函数,其中任何一个指针都可能为空,你会建议我怎么做?我该写256个重载函数吗?使用可空值类型或虚拟变量可以解决这个问题吗? - Calmarius
@Calmarius 为什么不写一个定义,用 IntPtr 替换所有的 ref <struct_name>?是的,这样做不太安全,但如果你处理 P/Invoke,代码安全似乎已经不是重点了。 - SerG

11

一种方法是创建一个虚拟变量,将其赋值为空,然后传递它。


5

虽然 @JaredPar 的 答案 毫无疑问是 正确 的答案,但还有另一个答案: 不安全 的代码和指针:

unsafe {
    Mapping* nullMapping = null;
    FILES_GetMemoryMapping(
            MapFile,
            out size,
            MapName,
            out PacketSize,
            ref *nullMapping,    // wat?
            out PagePerSector);
}

那看起来应该在运行时失败,但实际上并没有,因为 ref 和 * 相互抵消,导致 ref * nullMapping 的结果是空指针,这正是 FILES_GetMemoryMapping() 将接收的参数。
这可能不是一个好主意,但是它是可行的。

4
Mapping oMapping = null;

FILES_GetMemoryMapping(MapFile, out size, MapName, out PacketSize, ref oMapping, out PagePerSector);

3
"Mapping实际上是一个结构体,所以我不能将其设置为空。" - Nick
@Nick,请看一下我针对结构案例的回答。 - JaredPar
3
我认为这种做法没有抓住重点。有时候函数会有你并不关心的引用参数。定义无用变量会使代码混乱且让人感到烦恼。 - Spencer Ruport

3

使用C#语言7.2或更高版本,现在可以允许使用Null。只需像这样替换函数参数中的ref即可...

最初的回答:

void MyFunc(ref Object obj) { ... }

to...

void MyFunc(in Object obj) { ... }

这将允许您在应用程序中调用函数时将null作为参数值传递。对于对象和本机类型都适用,并且在语法上等同于ref readonly。"最初的回答"。

你的方法存在问题,虽然它允许传递 null,但从语义上讲已经不正确了,因为如果传递非空值,MyFunc 的实现可以修改 obj,但是你的代码却表明不能修改。.NET marshaller 是否忽略返回值(强制执行 readonly 限制)或将 in 解释为 ref(忽略该限制)? - Athari
@Athari 确实如此。我今天才弄清楚这个用法。如果您的函数还需要修改传递的对象,则根据所需修改的范围,此解决方案可能不适用。例如,在某些情况下,将传递的只读变量分配给本地变量以进行修改可能是可接受的。 - Jim Fell

3

您可以使用System.Runtime.CompilerServices.Unsafe类获取空引用。

ref Unsafe.AsRef<Mapping>(null)

1

兄弟,那就像在C语言中使用指针一样

public static extern unsafe uint FILES_GetMemoryMapping(
    [MarshalAs(UnmanagedType.LPStr)] string pPathFile,
    out ushort Size,
    [MarshalAs(UnmanagedType.LPStr)] string MapName,
    out ushort PacketSize,
    Mapping* oMapping,
    out byte PagesPerSector);

// somewhere in code
unsafe {
    uint result = FILES_GetMemoryMapping("path", out ushort size, "map", out ushort packetSize, null, out byte pages);
}


这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Julia

0
也许这不是最理想的答案,但如果您需要在调用函数时将null传递为参数,请考虑重载该函数,省略您要设置为null的变量的形式参数。
例如,假设您有一个函数看起来像这样:
public void MyFunction(string x, int y, ref string z) {...};

您想要能够将参数 z 传递为 null。 可以尝试创建一个新的 MyFunction 重载方法,类似于以下内容:

public void MyFunction(string x, int y) {...};

这种方法并不适合每个人的需求,但它是另一种可能的解决方案。


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