提问人:Brian Kessler 提问时间:2/8/2023 最后编辑:Brian Kessler 更新时间:2/9/2023 访问量:187
在 C# 中,如何创建或重载赋值运算符以可能同时赋值两个值?
In C#, How can I create or overload an assignment operator to possibly assign two values at once?
问:
这可能是一个愚蠢的问题,但以防万一......
我们有一个第三方软件包,其中包含奇怪的模型,例如:
public partial class CountingDevice
{
public int countingDeviceNo { get; set; }
public string countingDeviceName { get; set; }
public string obis { get; set; }
public int integralPart { get; set; }
public bool integralPartFieldSpecified;
public int fractionalPart { get; set; }
public bool fractionalPartFieldSpecified;
public double value { get; set; }
public bool valueFieldSpecified;
public bool offPeakFlag { get; set; }
public bool offPeakFlagFieldSpecified;
public ExpectedMeterReading expectedMeterReading { get; set; }
// snipped for brevity
}
您会注意到,有时存在成对的字段,例如 和 。integralPart
integralPartFieldSpecified
问题来了:如果我只是简单地分配一些值但不设置,则 的值将被完全忽略,从而导致解决方案失败。integralPart
integralPartFieldSpecified = true
integralPart
因此,当将我们自己的模型映射到这种疯狂时,我需要在代码中加入以下结构:
if (IntegralPart != null)
{
countingDevice.integralPartSpecified = true;
countingDevice.integralPart = (int)IntegralPart!;
}
为了减少代码行数和不被雷区绊倒,我想做以下任何一项:
A. 重载运算符,以便它自动检查一个属性,该属性是布尔值,并且“指定”连接到当前属性的名称。如果存在此类属性,则在赋值时将为其赋值;如果没有,则分配将照常运行。理想情况下,它应该足够“聪明”,可以分配“......如果分配的值为 null/default/empty,则指定“设置为 false。=
B. 创建一些客户运算符,其操作与 A 相同。
C. 创建一些方法,我可以以简洁且最好是类型安全的方式调用这些方法来执行相同的操作。
这可能吗? 如果是这样,如何?
为了明确:我需要构建相当多的包装器。 我不想对每个字段重复此逻辑,并担心遗漏它适用的某些字段。 如果“指定”字段存在,我想要一种通用方法,可以一次分配两个字段,如果不存在,则能够以完全相同的方式进行分配。
答:
不能在 C# 中重载运算符。=
您可以只使用自定义属性并在 setter 中设置“FieldSpecified”字段,例如
private int _integralPart;
public int integralPart
{
get { return _integralPart; }
set
{
_integralPart = value;
integralPartFieldSpecified = true;
}
}
public bool integralPartFieldSpecified;
更新
如果你想要一个泛型解决方案,你可以将泛型类用于你想要实现指定行为的属性,例如:
public class ValueWithSpecifiedCheck<T>
{
private T _fieldValue;
public T FieldValue
{
get
{
return _fieldValue;
}
set
{
_fieldValue = value;
FieldSpecified = true;
}
}
public bool FieldSpecified { get; set; }
}
public class Data
{
public ValueWithSpecifiedCheck<int> IntegralPart { get; set; }
}
然后,类/属性将按如下方式使用:
public static void Main()
{
var data = new Data();
data.IntegralPart = new ValueWithSpecifiedCheck<int>();
data.IntegralPart.FieldValue = 7;
Console.WriteLine(data.IntegralPart.FieldSpecified);// Prints true
}
评论
C# 不允许重载运算符(与 C++ 不同)。但是,您的建议 C 应该有效。这也有点麻烦,因为你必须编写一堆方法,但你可以编写一个扩展方法,例如=
public static class Extensions
{
public static void UpdateIntegralPart(this CountingDevice dev, double value)
{
dev.integralPart = value;
dev.integralPartSpecified = true;
}
}
然后你可以打电话
countingDevice.UpdateIntegralPart(1234);
评论
dynamic
不绊倒雷区
封装雷区。
如果您不控制此第三方 DTO,则不要在整个域中使用它。将此第三方工具的集成封装或包装在您控制的黑匣子中。然后在整个域中使用您的模型。
在此第三方系统的集成组件中,只需映射到您的域模型和此第三方 DTO。因此,在 DTO 上设置第二个字段的这一行额外代码仅存在于该位置。
评论
如果实现泛型解决方案并添加隐式转换运算符,则使用起来非常方便。
下面是一个示例结构(我把它做成一个以确保不可变的机制):Optional<T>
readonly struct
public readonly struct Optional<T> where T : struct
{
public Optional(T value)
{
_value = value;
}
public static implicit operator T(Optional<T> opt) => opt.Value;
public static implicit operator Optional<T>(T opt) => new(opt);
public T Value => _value!.Value;
public bool Specified => _value is not null;
public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;
readonly T? _value;
}
你可以用它来实现你的类,如下所示:CountingDevice
public partial class CountingDevice
{
public int countingDeviceNo { get; set; }
public string countingDeviceName { get; set; }
public string obis { get; set; }
public Optional<int> integralPart { get; set; }
public Optional<int> fractionalPart { get; set; }
public Optional<double> value { get; set; }
public Optional<bool> offPeakFlag { get; set; }
// snipped for brevity
}
由于隐式转换,使用是很自然的:
public static void Main()
{
var dev = new CountingDevice
{
integralPart = 10, // Can initialise with the underlying type.
value = 123.456
};
Console.WriteLine(dev.fractionalPart.Specified); // False
Console.WriteLine(dev.integralPart.Specified); // True
Console.WriteLine(dev.value); // 123.456
Console.WriteLine(dev.value.ToString()); // 123.456
Console.WriteLine(dev.fractionalPart.ToString()); // "<NONE>"
dev.fractionalPart = 42; // Can set the value using int.
Console.WriteLine(dev.fractionalPart.Specified); // True
Console.WriteLine(dev.fractionalPart); // 42
var optCopy = dev.offPeakFlag;
Console.WriteLine(optCopy.Specified); // False
dev.offPeakFlag = true;
Console.WriteLine(dev.offPeakFlag.Specified); // True
Console.WriteLine(optCopy.Specified); // Still False - not affected by the original.
Console.WriteLine(optCopy); // Throws an exception because its not specified.
}
您可能还希望使用可选的引用类型,但要做到这一点,您需要声明一个带有约束的泛型:class
public readonly struct OptionalRef<T> where T : class
{
public OptionalRef(T value)
{
_value = value;
}
public static implicit operator T(OptionalRef<T> opt) => opt.Value;
public static implicit operator OptionalRef<T>(T opt) => new(opt);
public T Value => _value ?? throw new InvalidOperationException("Accessing an unspecified value.");
public bool Specified => _value is not null;
public override string ToString() => _value is null ? "<NONE>" : _value.ToString()!;
readonly T? _value;
}
就我个人而言,我认为这有点矫枉过正。我只使用可为 null 的值类型等,但这取决于预期的用法。int?
double?
另一个(昂贵的)解决方案是编写一个方法,该方法采用对象、属性名称和新属性值。然后,您可以使用反射来设置指定属性的属性值,以及搜索要设置的字段(如果存在)。bool
请注意,您需要传递属性的正确类型。例如,没有编译时检查您是否传递的是双精度值而不是属性的字符串。value
下面,我在类型上创建了一个扩展方法,以简化在主代码中调用该方法的过程(该方法成为对象本身的成员):object
public static class Extensions
{
// Requires: using System.Reflection;
public static bool SetPropertyAndSpecified(this object obj,
string propertyName, object propertyValue)
{
// Argument validation left to user
// Check if 'obj' has specified 'propertyName'
// and set 'propertyValue' if it does
PropertyInfo prop = obj.GetType().GetProperty(propertyName,
BindingFlags.Public | BindingFlags.Instance);
if (prop != null && prop.CanWrite)
{
prop.SetValue(obj, propertyValue, null);
// Check for related "FieldSpecified" field
// and set it to 'true' if it exists
obj.GetType().GetField($"{propertyName}FieldSpecified",
BindingFlags.Public | BindingFlags.Instance)?.SetValue(obj, true);
return true;
}
return false;
}
}
将此类添加到项目后,可以执行如下操作:
static void Main(string[] args)
{
var counter = new CountingDevice();
// Note that 'valueFieldSpecified' and `integralPartFieldSpecified'
// are set to 'false' on 'counter'
// Call our method to set some properties
counter.SetPropertyAndSpecified(nameof(counter.integralPart), 42);
counter.SetPropertyAndSpecified(nameof(counter.value), 69d);
// Now 'valueFieldSpecified' and 'integralPartFieldSpecified'
// are set to 'true' on 'counter'
}
评论
this object obj
string propertyName
object propertyValue
this object
this SomeOtherType
object
评论
=