C#中可选ref参数的解决方法

时间:2011-06-06 09:02:29

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

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

示例代码(可行):

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(并将它们声明为bool?并不能真正解决我的问题)。我只想让调用者能够将null 作为参考

虽然该方法的实现可能更复杂,但我真的希望将调用保持在一行。 (即,不必为此调用声明临时变量。)

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

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

PS:你可能想知道我为什么要做一些奇怪的事情,有些背景话语:doSomething方法是图书馆的一部分。 doSomething的调用来自生成的C#代码。是的,将所有这些bool(在实际项目中约200个)作为单独的字段确实在大局中有意义,但推理与此问题无关。

9 个答案:

答案 0 :(得分:4)

<强> UPDATE ...

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

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等就可以 更清洁,并完全避免方法调用。

答案 1 :(得分:4)

如果确实希望拥有可选参数,那么唯一的解决方案就是使用指针,因为它们可以为空,与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 :(得分:1)

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

为什么不为您的功能编写新类型?它简单而干净。

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

答案 5 :(得分:1)

如果创建一个类来保存布尔参数,那该怎么办?也许称之为State或类似的东西。它会有bool?作为领域。

然后你可以将你的状态对象传递给你的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);
    }
}

答案 6 :(得分:1)

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

您可以使用数组或列表作为参数传入方法,但我假设您之后需要取消打包此列表以分配各个值。

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

This stackoverflow文章讨论了使用Func将值类型更改为引用类型。不确定它是否适用于您的解决方案,但我认为这是一个很好的方法。

答案 7 :(得分:0)

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

就个人而言,我会添加一个'in between'层,将所有布尔值转储成一个数组。

答案 8 :(得分:0)

你要做的是错误的:

  • 您正在尝试编写代码以减少更改和重组,但将来您将获得更多维护和复杂性

通常这应该是另一种方式:

  • 当你看到某些东西正在变得复杂且难以理解时进行重组,因为你会等待更多的困难它会变得更难以在某些时候你会被卡住

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

使用虚拟参考参数:

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