【问题标题】:Workaround for optional ref parameters in C#C# 中可选 ref 参数的解决方法
【发布时间】:2011-09-09 03:13:30
【问题描述】:

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

示例代码(可行):

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

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

虽然方法的实现可能更复杂,但我真的希望将调用减少到一行。 (即避免只为这个调用声明临时变量。)

一种可能性是为函数声明(八)个重载,无论是否给出所有布尔组合,但我需要提出一些方案来确保它们都有唯一的签名。 (我坚持使用 C# 3.0,所以没有命名参数。)

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

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

【问题讨论】:

  • @BoltClock 是的,谢谢。我总是把这些版本号混在一起。我在问题中修复了它。
  • 上帝,感谢您提供的所有出色答案。 :) 我没想到有人会关心由非常不常见的要求引起的我公认的非常奇怪的问题。 :)

标签: c# c#-3.0 optional-parameters ref


【解决方案1】:

如果你真的想要有可选参数,你唯一的解决方案是使用指针,因为它们可以为空,不像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;
}

丑陋无处不在。但是,嘿,可选参数!

【讨论】:

  • 我也在考虑这个问题。我想这比“传递带有变量名的字符串,然后使用反射来解决它们”的方法要好。但是,是的,我也想避免不安全——或者至少完全确定没有更好的方法可以先做到这一点。
【解决方案2】:

更新...

如果您需要在单个方法调用中潜在地操作所有三个字段,那么我认为您需要使用反射来相对干净地完成它。然而,这可以通过使用表达式树的某种程度的类型安全来完成;您无需求助于将字段名称作为字符串传递。

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 等并完全避免方法调用会更加简洁。

【讨论】:

  • 问题是布尔值的任何组合都是可能的,并且调用者选择的组合会影响结果,所以我实际上需要其中的九个辅助方法。尽管如此,这可能是一个很好的解决方案。我可能可以写一次,然后通过接受“结果”(基本上可能的结果状态为 0..8),在外部调用实际的工作方法(您的DoCommonStuff)并将结果状态传入,从而在任何地方使用它们。跨度>
  • 是的,我可能简化了我的示例代码。但是,谢谢,您可能已将我引向正确的道路!
  • 我的意思是八个辅助方法,而不是九个。 2^3,呵呵。
  • @hheimbuerger:所有布尔字段是否与您的 doSomething 方法位于同一类中,还是散布在各处?
  • @LukeH 他们住在一个地方,但它与doSomething 方法不同。
【解决方案3】:

您不能只是将值或空引用作为ref 参数传递,它必须是一个变量。您可能知道,值类型不能为空,除非您将它们设为可空。

C# 4.0 does not allow specifying default values for optional ref / out parameters,所以除了繁琐的方法重载之外,我不相信有任何可行的方法可以用 C# 3.0 解决这个问题。

【讨论】:

    【解决方案4】:

    创建您自己的容器类型并传递给您的方法怎么样?这应该是非常简单的解决方案。

    【讨论】:

    • 我可以将引用 (ref bool) 存储在容器中吗?那么它们(引用,而不是值)是否可以为空?
    • @hheimbuerger,我相信它们不会变为可空的。但是您的对象将是引用类型,因此可以为空。
    • @Oleg 因此,您正在考虑八种不同的容器类型,其中包含给定和未给定标志的所有组合,例如 Dennis Bischof。
    • @hheimbuerger, No. doSomething(YourContainer c1, YourContainer c2, YourContainer c3) { if(c1 != null){ c1.Value= true; } if(c2!=null) {c2.Value=false;} if(c3!=null) {c3.Value = !c3.Value}}
    • doSomething(new YourContainer(ref yourBool), null, null);
    【解决方案5】:

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

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

    【讨论】:

    • 但这只会改变新类型中字段的状态(MyNewTypeWith3Bools,...)。之后如何将值传输回原始字段?如果我不需要更改对象的实际状态,我可以将它们作为值类型传入布尔值(bool 参数而不是ref bool)。
    • 另外,我实际上需要以下八种类型:MyNewTypeWithBools1And2、MyNewTypeWithBools1And3、MyNewTypeWithBools2And3 等。
    【解决方案6】:

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

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

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

    Thisstackoverflow 帖子讨论了使用 Func 将值类型更改为引用类型。不确定它是否适合您的解决方案,但我认为这是一个不错的方法。

    【讨论】:

    • 可以将我所有的布尔值更改为辅助类型。感谢您的链接,我会考虑该解决方案。
    【解决方案7】:

    如果你创建一个类来保存布尔参数呢?也许叫它State 或类似的东西。它会有布尔吗?作为字段。

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

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

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

    【讨论】:

    • 不确定我将如何将结果从 State 实例转移回原始字段。
    • @hheimbuerger。这是一个不好的例子。我对其进行了更改,因此它显示了如何在该方法之后再次使用该变量。
    • 不过,我仍然需要将结果恢复到原始字段中。我有大约 200 个这些布尔值被其余代码使用,因此从 state 类访问它们不是一种选择。 (是的,我知道,我的要求真的很奇怪。:))
    【解决方案8】:

    这似乎是一个需要代码生成器的问题。为您需要的所有函数创建重载,或者仅通过一个小代码生成器创建您需要的所有包装器。

    我个人会添加一个“中间”层来将所有布尔值转储到一个数组中。

    【讨论】:

      【解决方案9】:

      你试图做的事情是错误的:

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

      通常这应该以其他方式进行:

      • 当您看到某些东西正在变得或将变得复杂且难以阅读时进行重组,因为您等待的时间越长,它就会变得越困难,并且在某些时候您会卡住

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

      使用虚拟参考参数:

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

      这可能会奏效,但正如您清楚地看到的那样,这是一个 hack...

      您可能会抱怨您需要在需要调用它的所有地方声明虚拟变量,但事实并非如此。您可以破解黑客并全面破解以简化调用代码,即使这样做并不酷:

          public static class dummy
          {
              public static bool boolean;
          }
      
      
      //...
      
         doSomething(ref b1, ref dummy.boolean, ref b5, ref dummy.boolean, ref b7);
      

      【讨论】:

        猜你喜欢
        • 2013-03-08
        • 1970-01-01
        • 2013-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-25
        • 1970-01-01
        • 2018-05-27
        相关资源
        最近更新 更多