C# 中结构的隐式运算符问题
Implicit operator issue with struct in C#
我正在尝试定义一个助手 class 作为触发器。
行为是当 第一次 测试 true/false 时,它应该 return 为真,并且对于所有后续调用将为假(因此,将用于一次性操作):
public struct Trigger
{
private bool converted;
private bool disposed;
public static implicit operator bool(Trigger trigger)
{
if (trigger.disposed)
{
return false;
}
// Value will only be true once.
if (!trigger.converted)
{
trigger.converted = true;
trigger.disposed = true;
}
return trigger.converted;
}
}
问题是,隐式运算符 总是 return 为真
从 struct 更改为 class 时它工作正常。
关于定义隐式运算符,我在这里遗漏的结构有什么特别之处吗?
cast-to-bool 运算符强制复制,因此它总是看到一个新的 Trigger 对象。
通过将其设为实例方法来强制它使用相同的方法。 (我是作为 属性 来做的,但它也可以是一种方法)。我也简化了一下。
struct Trigger
{
private bool triggered;
public bool IsFresh
{
get
{
bool t = this.triggered;
this.triggered = true;
return !t;
}
}
}
void Main()
{
Trigger T = new Trigger();
var a = T.IsFresh;
var b = T.IsFresh;
var c = T.IsFresh;
Console.WriteLine("{0} {1} {2}", a,b,c);
}
您的问题的评论已经包含答案。
原因是当值类型作为方法(或本例中的"operator")的参数传递时,会生成整个对象的副本。因此,您的字段 converted
和 disposed
的更新发生在对象的副本上。当控件离开该方法时,不会保留该副本。
如果你制作了一个 ref
参数,那会有所帮助,但在运算符中不允许这样做(有充分的理由),所以它看起来像:
// just to explain, not a recommended "solution"
public static bool ToBool(ref Trigger trigger) // ByRef parameter
{
.... // change 'trigger' here and return value
}
当然,实例方法(或实例 属性)也有帮助,请参阅 James Curran 的回答。
如果结构包含引用类型的字段,看看会发生什么是很有启发性的。我选择引用类型 bool[]
,它是一个数组类型。我想要一个可以变异的长度数组。当structTrigger
传值时,字段的"value"是对同一个数组实例的引用
// just to explain, not a recommended "solution"
public struct Trigger
{
bool[] referenceTypeField; // meant to always hold a length-one array of bools
public static Trigger GetNew()
{
return new Trigger { referenceTypeField = new[] { false, }, };
}
public static implicit operator bool(Trigger trigger)
{
if (trigger.referenceTypeField == null)
throw new InvalidOperationException("Always construct Trigger through the GetNew method");
if (trigger.referenceTypeField[0])
return false;
trigger.referenceTypeField[0] = true;
return true;
}
}
测试:
var t = Trigger.GetNew();
Console.WriteLine(t); // True
Console.WriteLine(t); // False
Console.WriteLine(t); // False
当然我们看到我们真的只是把null
问题移到了现场。 default(Trigger)
无效,因为它的字段是 null
引用。
即将推出 C# 的未来版本,其中结构可以具有零参数实例构造函数,并且可以具有实例字段的字段初始值设定项。使用该版本的 C#,您可以:
public struct Trigger
{
readonly bool[] referenceTypeField = new[] { false, }; // ALLOWED!
public static implicit operator bool(Trigger trigger)
{
if (trigger.referenceTypeField == null)
throw new InvalidOperationException("Always construct Trigger properly");
if (trigger.referenceTypeField[0])
return false;
trigger.referenceTypeField[0] = true;
return true;
}
}
由于像您这样的问题,人们常说可变结构是邪恶的(在 Stack Overflow 上搜索这方面的话题)。最好的建议是放弃使用可变结构的想法(并且不要使用我上面的任何示例代码)。
我正在尝试定义一个助手 class 作为触发器。
行为是当 第一次 测试 true/false 时,它应该 return 为真,并且对于所有后续调用将为假(因此,将用于一次性操作):
public struct Trigger
{
private bool converted;
private bool disposed;
public static implicit operator bool(Trigger trigger)
{
if (trigger.disposed)
{
return false;
}
// Value will only be true once.
if (!trigger.converted)
{
trigger.converted = true;
trigger.disposed = true;
}
return trigger.converted;
}
}
问题是,隐式运算符 总是 return 为真
从 struct 更改为 class 时它工作正常。
关于定义隐式运算符,我在这里遗漏的结构有什么特别之处吗?
cast-to-bool 运算符强制复制,因此它总是看到一个新的 Trigger 对象。
通过将其设为实例方法来强制它使用相同的方法。 (我是作为 属性 来做的,但它也可以是一种方法)。我也简化了一下。
struct Trigger
{
private bool triggered;
public bool IsFresh
{
get
{
bool t = this.triggered;
this.triggered = true;
return !t;
}
}
}
void Main()
{
Trigger T = new Trigger();
var a = T.IsFresh;
var b = T.IsFresh;
var c = T.IsFresh;
Console.WriteLine("{0} {1} {2}", a,b,c);
}
您的问题的评论已经包含答案。
原因是当值类型作为方法(或本例中的"operator")的参数传递时,会生成整个对象的副本。因此,您的字段 converted
和 disposed
的更新发生在对象的副本上。当控件离开该方法时,不会保留该副本。
如果你制作了一个 ref
参数,那会有所帮助,但在运算符中不允许这样做(有充分的理由),所以它看起来像:
// just to explain, not a recommended "solution"
public static bool ToBool(ref Trigger trigger) // ByRef parameter
{
.... // change 'trigger' here and return value
}
当然,实例方法(或实例 属性)也有帮助,请参阅 James Curran 的回答。
如果结构包含引用类型的字段,看看会发生什么是很有启发性的。我选择引用类型 bool[]
,它是一个数组类型。我想要一个可以变异的长度数组。当structTrigger
传值时,字段的"value"是对同一个数组实例的引用
// just to explain, not a recommended "solution"
public struct Trigger
{
bool[] referenceTypeField; // meant to always hold a length-one array of bools
public static Trigger GetNew()
{
return new Trigger { referenceTypeField = new[] { false, }, };
}
public static implicit operator bool(Trigger trigger)
{
if (trigger.referenceTypeField == null)
throw new InvalidOperationException("Always construct Trigger through the GetNew method");
if (trigger.referenceTypeField[0])
return false;
trigger.referenceTypeField[0] = true;
return true;
}
}
测试:
var t = Trigger.GetNew();
Console.WriteLine(t); // True
Console.WriteLine(t); // False
Console.WriteLine(t); // False
当然我们看到我们真的只是把null
问题移到了现场。 default(Trigger)
无效,因为它的字段是 null
引用。
即将推出 C# 的未来版本,其中结构可以具有零参数实例构造函数,并且可以具有实例字段的字段初始值设定项。使用该版本的 C#,您可以:
public struct Trigger
{
readonly bool[] referenceTypeField = new[] { false, }; // ALLOWED!
public static implicit operator bool(Trigger trigger)
{
if (trigger.referenceTypeField == null)
throw new InvalidOperationException("Always construct Trigger properly");
if (trigger.referenceTypeField[0])
return false;
trigger.referenceTypeField[0] = true;
return true;
}
}
由于像您这样的问题,人们常说可变结构是邪恶的(在 Stack Overflow 上搜索这方面的话题)。最好的建议是放弃使用可变结构的想法(并且不要使用我上面的任何示例代码)。