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
到目前为止,一切都很好。现在,这些参数在方法上应该是可选的。也就是说,呼叫者可以选择例如使用setTrue
和invert
效果,但不能选择使用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)作为单独的字段确实从全局来看是有意义的,但推理与这个问题并不真正相关。
如果真的想要有可选的参数,你唯一的解决方案就是使用指针,因为它们可以是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 = true
、b2 = false
、b3 = !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;
}
...
您将永远无法使用ref
或out
参数执行doSomething(ref b1, null, ref b5);
(即传递null作为参数),因为ref
或out
参数必须是可赋值变量。
您可以使用一个数组或列表作为参数传递给该方法,但我认为之后您需要解压缩该列表来分配各个值。
另一种选择是创建自己的布尔类型来包装布尔值,但这需要更改对布尔值的所有引用。
这篇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);