7

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

示例代码(有效):

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

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

基本上,我想做的是:

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 个)作为单独的字段在大局中确实有意义,但推理与这个问题并不真正相关。

4

9 回答 9

4

更新...

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

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

于 2011-06-06T09:12:33.467 回答
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;
}

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

于 2011-06-06T09:18:16.787 回答
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;
}
...
于 2011-06-06T09:20:24.650 回答
1

如果你创建一个类来保存布尔参数怎么样。也许叫它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);
    }
}
于 2011-06-06T09:20:38.317 回答
1

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

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

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

这篇stackoverflow 帖子讨论了使用Func. 不确定它是否适合您的解决方案,但我认为这是一个不错的方法。

于 2011-06-06T09:21:33.183 回答
1

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

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

于 2011-06-06T09:15:26.030 回答
1

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

于 2011-06-06T09:18:31.950 回答
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);
于 2011-06-06T10:47:57.687 回答
0

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

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

于 2011-06-06T09:14:57.463 回答