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

发布于 2024-11-14 04:12:47 字数 1852 浏览 9 评论 0 原文

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

示例代码(有效):

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

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

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

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

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

I'm trying to write a method that takes references to boolean flags and modify them. The booleans are all declared separately (i.e. not in an indexable data structure) and the caller of the method should be able to decide which booleans are being modified.

Example code (this works):

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

Output, as expected:

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

So far, so good. Now these parameters should be optional on the method. That is, the caller can choose to e.g. use the setTrue and the invert effect, but not the setFalse one.

Basically, what I'd like to do is this:

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

And then declare the doSomething method like this:

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

Note that I do not want to check if the value is null. The values are real bools and can't be null (and declaring them as bool? doesn't really solve my problem). I only want to give the caller the ability to give null as the reference.

While the implementation of the method may be more complex, I'd really like to keep the invocation down to one line. (I.e. avoid having to declare temporary variables just for this call.)

One possibility would be to declare (eight) overloads for the function with all combinations of bools given or not, but then I need to come up with some scheme to make sure they all have unique signatures. (I'm stuck with C# 3.0, so no named parameters.)

Am I missing something? Is there a clean workaround? Currently the only (barely) acceptable alternative I can think of is to pass in strings with the variable names (or null) and then resolve these to the actual field using reflection.

PS: As you're probably wondering why I trying to do something this strange, some words of background: the doSomething method is part of a library. The invocations of doSomething are coming from generated C# code. And yes, having all these bools (~200 in the real project) as separate fields does make sense in the big picture, but the reasoning isn't really relevant for this question.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

眼眸里的那抹悲凉 2024-11-21 04:12:47

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

浑身都是丑陋的。但是嘿,可选参数!

If you really want to have optional parameters, your only solution would be to use pointers, since they can be null, unlike 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;
}

Uglyness all over. But hey, optional parameters !

埋葬我深情 2024-11-21 04:12:47

更新...

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

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更清晰> 直接等并完全避免方法调用。

UPDATE...

If you need to potentially manipulate all three fields in a single method call then I think you'll need to use reflection to do it relatively cleanly. This can, however, be done with some degree of type-safety using expression trees; you don't need to resort to passing in the field names as strings.

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

Note that using reflection like this will be relatively slow. If it's not fast enough then you might need to dive a bit deeper into reflection, possibly using DynamicMethod to create delegates and then cache them in a dictionary for re-use. (Though I wouldn't bother with that unless you're sure that plain reflection is holding you back.)


ORIGINAL ANSWER...

Wouldn't it be a lot cleaner to have separate methods and just call them as needed rather than attempting to roll it all into a single method?

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()
{
    // ...
}

I'm assuming that there is some common stuff that also needs doing. If not then it would be much cleaner to simply do b1 = true, b2 = false, b3 = !b3 etc directly and avoid the method calls altogether.

ゞ记忆︶ㄣ 2024-11-21 04:12:47

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

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

You simply can't just pass a value or a null reference as a ref argument, it must be a variable. And as you probably know, value types cannot be nullable unless you make them nullable.

C# 4.0 does not allow specifying default values for optional ref / out parameters, so I don't believe there is any feasible way to get around this with C# 3.0 either, besides forests of cumbersome method overloads.

甜警司 2024-11-21 04:12:47

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

What about creating your own container type, to pass into your method? This should be quite simple solution.

戏剧牡丹亭 2024-11-21 04:12:47

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

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

Why you don't write a new type only for your functions? It's easy and clean.

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;
}
...
够钟 2024-11-21 04:12:47

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

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

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

这篇 stackoverflow 帖子讨论了更改使用 Func 将值类型转换为引用类型。不确定它对您的解决方案是否可行,但我认为这是一个很好的方法。

You will never be able to do doSomething(ref b1, null, ref b5); (i.e. pass null as an argument) with ref or out parameters because the ref or out argument has to be an assignable variable.

You could use an array or list to pass in as a parameter to the method but I assume then you will need to un-pack this list afterwards to assign the individual values.

Another option is to create your own boolean type to wrap the boolean value but this would require all your references to boolean to be changed.

This stackoverflow post discusses changing a value type to a reference type using a Func. Not sure if its feasible for your solution but I think its a nice approach.

浅蓝的眸勾画不出的柔情 2024-11-21 04:12:47

如果您创建一个类来保存布尔参数怎么样?也许可以将其称为“状态”或类似的名称。它会有布尔吗?作为字段。

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

How about if you create a class to hold the boolean parameters. Maybe call it State or something similar. It will have bool? as fields.

Then you can pass your state object to your doSomethingMethod which will call the appropriate generated method and will pass all the parameters by ref.

The good thing about this method is you only need to set the booleans you are going to use and the wrapper method will figure out what to do. e.g. if some of the parameters are not set it's not going to pass them to the generated method.

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
}

Then you other method can call this cleanly

class Program
{
    static void Main(string[] args)
    {
        var state = new MyBooleanState() { Invert = false };
        doSomething(state);
        Console.WriteLine("Invert is now: {0}", state.Invert);
    }
}
强者自强 2024-11-21 04:12:47

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

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

This seems like a problem that just screams for a code generator. Create overloads for all the functions you need or just create all the wrappers you need from a little code generator.

Personally I would add a 'in between' layer to dump all the booleans into an array.

流心雨 2024-11-21 04:12:47

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

  • 你试图以一种减少更改和重组的方式编写代码,但将来你将有更多的维护和复杂性

通常这应该以另一种方式进行:

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

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

使用虚拟引用参数:

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

这可能会工作,但正如你清楚地看到它是一个黑客......

你可能会抱怨你需要在所有需要调用它的地方声明虚拟变量,但事实并非如此。您可以对 hacks 进行修改并进行修改以简化调用代码,尽管这样做并不酷:

    public static class dummy
    {
        public static bool boolean;
    }


//...

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

What you are trying to do is wrong:

  • you are trying to write code in a way to make fewer changes and restructuring but in the future you will have more maintenance and complexity

Usually this should go in the other way:

  • do restructuring when you see something is becoming or will become complex and unreadable because more you wait more difficult it will become and at some point you will get stuck

Now to answer your question with a non best practice answer, because without restructuring there are none:

Use dummy ref parameters:

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

This will probably work, but as you clearly see it is a hack...

You may complain that you will need to declare dummy variables in all places where you need to call this, but that is not true. You can hack on hacks and hack all over to simplify invoking code, even though it is not cool to do that:

    public static class dummy
    {
        public static bool boolean;
    }


//...

   doSomething(ref b1, ref dummy.boolean, ref b5, ref dummy.boolean, ref b7);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文