c#3.0-c#中可选ref参数的解决方法

本文关键字:参数 解决 方法 ref 0-c# c#3 | 更新日期: 2023-09-27 17:58:24

我正在尝试编写一个方法,该方法引用布尔标志并对其进行修改。布尔值都是单独声明的(即不在可索引的数据结构中),方法的调用方应该能够决定要修改哪些布尔值。

示例代码(有效):

class Program
{
    private static bool b1, b2, b3, b4, b5;
    private static void doSomething(ref bool setTrue, ref bool setFalse, ref bool invert)
    {
        setTrue = true;
        setFalse = false;
        invert = !invert;
    }
    static void Main(string[] args)
    {
        Console.WriteLine("Pre: {0}, {1}, {2}, {3}, {4}", b1, b2, b3, b4, b5);
        doSomething(ref b1, ref b3, ref b5);
        Console.WriteLine("Post: {0}, {1}, {2}, {3}, {4}", b1, b2, b3, b4, b5);
    }
}

输出,如预期:

Pre: False, False, False, False, False
Post: True, False, False, False, True

到目前为止,一切都很好。现在,这些参数在方法上应该是可选的。也就是说,呼叫者可以选择例如使用setTrueinvert效果,但不能选择使用setFalse效果。

基本上,我想做的是:

doSomething(ref b1, null, ref b5); // error CS1503: Argument 2: cannot convert from '<null>' to 'ref bool'

然后像这样声明doSomething方法:

private static void doSomething(ref bool setTrue, ref bool setFalse, ref bool invert)
{
    if(setTrue != null) setTrue = true;
    if(setFalse != null) setFalse = false;
    if(invert != null) invert = !invert;
}

请注意,我不想检查值是否为null。这些值是真正的布尔值,不能为null(将它们声明为bool?并不能真正解决我的问题)。我只想让调用者能够将null作为引用

虽然该方法的实现可能更复杂,但我确实希望将调用保持为一行。(也就是说,避免只为这个调用声明临时变量。)

一种可能性是在给定或不给定布尔的所有组合的情况下为函数声明(八个)重载,但随后我需要想出一些方案来确保它们都具有唯一的签名。(我一直使用C#3.0,所以没有命名的参数。)

我是不是错过了什么?有没有干净的解决方法?目前,我能想到的唯一(勉强)可接受的替代方案是传入带有变量名(或null)的字符串,然后使用反射将这些字符串解析为实际字段。

PS:你可能想知道我为什么要做这样奇怪的事情,一些背景词:doSomething方法是库的一部分。doSomething的调用来自生成的C#代码。是的,将所有这些bool(在实际项目中约为200)作为单独的字段确实从全局来看是有意义的,但推理与这个问题并不真正相关。

c#3.0-c#中可选ref参数的解决方法

如果真的想要有可选的参数,你唯一的解决方案就是使用指针,因为它们可以是null,而不是ref

private static unsafe void doSomething(bool* setTrue, bool* setFalse, bool* invert)
{
    if (setTrue  != null) *setTrue  = true;
    if (setFalse != null) *setFalse = false;
    if (invert   != null) *invert   = !*invert;
}

到处都是丑女。但是嘿,可选参数!

更新

如果您需要在一个方法调用中潜在地操作所有三个字段,那么我认为您需要使用反射来相对干净地执行。然而,这可以通过使用表达式树在一定程度上实现类型安全;您不需要将字段名作为字符串传入。

DoSomething(() => b1, () => b3, () => b5);
DoSomething(() => b1, null, () => b5);
// ...
public static void DoSomething(Expression<Func<bool>> trueFieldSelector,
                               Expression<Func<bool>> falseFieldSelector,
                               Expression<Func<bool>> invertFieldSelector)
{
    FieldInfo fieldInfo;
    object obj;
    if (GetInfo(trueFieldSelector, out fieldInfo, out obj))
        fieldInfo.SetValue(obj, true);
    if (GetInfo(falseFieldSelector, out fieldInfo, out obj))
        fieldInfo.SetValue(obj, false);
    if (GetInfo(invertFieldSelector, out fieldInfo, out obj))
        fieldInfo.SetValue(obj, !(bool)fieldInfo.GetValue(obj));
}
private static bool GetInfo(Expression<Func<bool>> fieldSelector,
                            out FieldInfo fieldInfo, out object obj)
{
    if (fieldSelector == null)
    {
        fieldInfo = null;
        obj = null;
        return false;
    }
    var me = fieldSelector.Body as MemberExpression;
    if (me == null)
        throw new ArgumentException("Select a field!", "fieldSelector");
    fieldInfo = me.Member as FieldInfo;
    if (fieldInfo == null)
        throw new ArgumentException("Select a field!", "fieldSelector");
    var ce = me.Expression as ConstantExpression;
    obj = (ce == null) ? null : ce.Value;
    return true;
}

请注意,像这样使用反射将相对较慢。如果速度不够快,那么您可能需要深入思考,可能需要使用DynamicMethod创建委托,然后将它们缓存在字典中以供重用。(尽管我不会为此而烦恼,除非你确信这是一种纯粹的反思。)


原始答案

拥有单独的方法并根据需要调用它们,而不是试图将其全部集成到一个方法中,这不是更干净吗?

SetTrue(ref b1);
SetFalse(ref b3);
Invert(ref b5);
// ...
public static void SetTrue(ref bool field)
{
    DoCommonStuff();
    field = true;
}
public static void SetFalse(ref bool field)
{
    DoCommonStuff();
    field = false;
}
public static void Invert(ref bool field)
{
    DoCommonStuff();
    field = !field;
}
private static void DoCommonStuff()
{
    // ...
}

我假设有一些常见的事情也需要做。如果没有,那么简单地直接执行b1 = trueb2 = falseb3 = !b3等并完全避免方法调用将是更干净的

您不能简单地将值或空引用作为ref参数传递,它必须是一个变量。正如您可能知道的那样,除非您使值类型可以为null,否则它们不可能为null。

C#4.0不允许为可选的ref/out参数指定默认值,所以我认为除了繁琐的方法重载森林之外,C#3.0也没有任何可行的方法来解决这个问题。

创建自己的容器类型以传递到方法中怎么样?这应该是一个非常简单的解决方案。

为什么不只为函数编写新类型?它既简单又干净。

private static void doSomething(MyNewTypeWith3Bools object)
{    
    object.SetTrue = true;
    object.SetFalse = false;
    object.Invert = !object.Invert;
}
private static void doSomething(MyNewTypetWith2Bools object)
{    
    object.SetTrue = true;
    object.Invert = !object.Invert;
}
...

您将永远无法使用refout参数执行doSomething(ref b1, null, ref b5);(即传递null作为参数),因为refout参数必须是可赋值变量。

您可以使用一个数组或列表作为参数传递给该方法,但我认为之后您需要解压缩该列表来分配各个值。

另一种选择是创建自己的布尔类型来包装布尔值,但这需要更改对布尔值的所有引用。

这篇stackoverflow文章讨论了使用Func将值类型更改为引用类型。不确定这对你的解决方案是否可行,但我认为这是一个不错的方法。

如果您创建一个类来保存布尔参数,效果如何。也许可以称之为State或类似的东西。会有嘘声吗?作为字段。

然后,您可以将您的状态对象传递给您的doSomethingMethod,它将调用适当的生成方法,并通过引用传递所有参数。

这个方法的好处是,你只需要设置要使用的布尔值,包装器方法就会知道该怎么做。例如,如果一些参数没有设置,它就不会将它们传递给生成的方法。

public class MyBooleanStateClass
{ 
  public bool? setTrue { get; set; }
  public bool? setFalse { get; set; }
  public bool? Invert { get; set; }
}
private static void doSomething(MyBooleanStateClass state)
{
   if(state.setTrue.HasValue())
     // do something with setTrue
   // repeat for all the others
}

然后你的其他方法可以干净地调用这个

class Program
{
    static void Main(string[] args)
    {
        var state = new MyBooleanState() { Invert = false };
        doSomething(state);
        Console.WriteLine("Invert is now: {0}", state.Invert);
    }
}

这似乎是一个需要代码生成器解决的问题。为所有需要的函数创建重载,或者只需从一个小代码生成器中创建所有需要的包装器。

就我个人而言,我会添加一个"中间层",将所有布尔值转储到一个数组中。

您试图做的是错误的:

  • 您正在尝试以一种减少更改和重组的方式编写代码,但在未来,您将面临更多的维护和复杂性

通常情况下,这应该是另一种方式:

  • 当你看到某件事变得或将变得复杂和不可读时,进行重组,因为你等待的时间越长,它就会变得越困难,在某个时候你会陷入困境

现在用一个非最佳实践答案来回答你的问题,因为没有重组就没有:

使用伪参考参数:

doSomething(ref b1, ref dummy, ref b5, ref dummy, ref b7);

这可能会奏效,但正如你清楚地看到的,这是一个黑客。。。

您可能会抱怨需要在所有需要调用此函数的地方声明伪变量,但事实并非如此。你可以破解破解和破解所有的代码来简化调用,即使这样做并不酷:

    public static class dummy
    {
        public static bool boolean;
    }

//...
   doSomething(ref b1, ref dummy.boolean, ref b5, ref dummy.boolean, ref b7);