在C#中,如何创建或重载赋值运算符以可能一次分配两个值?

huangapple go评论70阅读模式
英文:

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; }
    // 为简洁起见已剪切部分
}

您会注意到有时存在一对字段,如 integralPartintegralPartFieldSpecified

问题在于,如果我简单地为 integralPart 赋予一些值,但没有设置 integralPartFieldSpecified = true,则 integralPart 的值将被完全忽略,导致解决方案失败。

因此,在将我们自己的模型映射到这种混乱的情况时,我需要在代码中加入如下的结构:

if (IntegralPart != null)
{
    countingDevice.integralPartSpecified = true;
    countingDevice.integralPart = (int)IntegralPart!;
}

出于减少代码行数和避免遇到潜在问题的兴趣,我想执行以下任一操作之一:

A. 重载 = 运算符,以便自动检查一个布尔属性,该属性的名称与当前属性的名称连接了 "Specified"。如果存在这样的属性,当赋值时,它将被赋值为 true;如果不存在,那么赋值将正常进行。理想情况下,它应该足够 "智能",以便在分配的值为 null/default/empty 时将 "Specified" 赋值为 false。

B. 创建一个自定义运算符,执行与 A 相同的操作。

C. 创建一个方法,我可以以简洁且最好是类型安全的方式调用,执行相同的操作。

这是否可能?
如果可能,如何实现?

为了明确问题: 我需要构建相当多的包装器。
我不想为每个字段重复此逻辑,也不想担心漏掉适用于它的某些字段。
我想要一种通用的方法,如果存在 "Specified" 字段,可以同时赋值两个字段,并且如果不存在该字段,则能以完全相同的方式进行赋值。

英文:

This is probably a stupid question, but just in case....

We have a 3rd party package with weird models like:

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
}

You'll notice that sometimes there are pairs of fields like integralPart and integralPartFieldSpecified.

Here is the problem: If I simply assign some value to integralPart but do not set integralPartFieldSpecified = true, the value of integralPart will be completely ignored causing the solution to fail.

So when mapping our own models to this madness, I need to litter the code with constructs like:

if (IntegralPart != null)
{
    countingDevice.integralPartSpecified = true;
	countingDevice.integralPart = (int)IntegralPart!;
}

Both in the interest of reducing lines of code and not stumbling over a minefield, I would like to do any one of the following:

A. Overload the = operator so it will automatically check for a property which is a boolean and has "Specified" concatenated to the current property's name. If such a property exists, it will be assigned true when the value is assigned; if not, then assignment will operate as normal. Ideally, it should be "smart" enough to assign "...Specified" to false if the value assigned is null/default/empty.

B. Create some customer operator which will do the same as A.

C. Create some method which I could invoke in a concise and preferably typesafe way to do the same.

Is this possible?
If so, how?

To make it clear: I need to build quite a few wrappers.
I don't want to repeat this logic for every field and worry about missing some fields which it applies to.
I want a generic way of assigning both fields at once if the "Specified" field exists and being able to do assignments in exactly the same way if it does not exist.

答案1

得分: 3

不要在地雷领域绊倒

封装地雷领域。

如果你不能控制这个第三方DTO,那么不要在整个领域中使用它。在你可以控制的黑匣子中封装或包装这个第三方工具的集成。然后在你的领域中使用你的模型。

在这个第三方系统的集成组件中,只需将你的领域模型和这个第三方DTO之间进行映射。因此,在设置DTO的第二个字段的这一行额外代码只存在于那一个地方。

英文:

> not stumbling over a minefield

Encapsulate the minefield.

If you don't control this 3rd party DTO then don't use it throughout your domain. Encapsulate or wrap the integration of this 3rd party tool within a black box that you control. Then throughout your domain use your models.

Within the integration component for this 3rd party system, simply map to/from your Domain Models and this 3rd party DTO. So this one extra line of code which sets a second field on the DTO only exists in that one place.

答案2

得分: 1

不能在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);// 打印 true
}
英文:

You cannot overload the = operator in C#.

You can just use custom properties and set the "FieldSpecified" fields in the setters e.g.

private int _integralPart;
public int integralPart 
{ 
	get { return _integralPart; }
	set
	{
		_integralPart = value;
		integralPartFieldSpecified = true;
	}
}
public bool integralPartFieldSpecified;

Update

If you want a generic solution you can use a generic class for properties that you want to achieve the specified behaviour with e.g.

public class ValueWithSpecifiedCheck&lt;T&gt;
{
	private T _fieldValue;

	public T FieldValue
	{
		get
		{
			return _fieldValue;
		}
		set
		{
			_fieldValue = value;
			FieldSpecified = true;
		}
	}

	public bool FieldSpecified { get; set; }
}

public class Data
{
	public ValueWithSpecifiedCheck&lt;int&gt; IntegralPart { get; set; }
}

Then the class/property would be used as following:

public static void Main()
{
	var data = new Data();

	data.IntegralPart = new ValueWithSpecifiedCheck&lt;int&gt;();
	data.IntegralPart.FieldValue = 7;
	Console.WriteLine(data.IntegralPart.FieldSpecified);// Prints true
}

答案3

得分: 1

以下是您要翻译的内容:

If you implement a generic solution and add implicit conversion operators, it's quite convenient to use.

Here's a sample Optional&lt;T&gt; struct (I made it a readonly struct to ensure immutable mechanics):

public readonly struct Optional<T> where T : struct
{
public Optional(T value)
{
_value = value;
}

public static implicit operator T(Optional&lt;T&gt; opt) =&gt; opt.Value;
public static implicit operator Optional&lt;T&gt;(T opt) =&gt; new(opt);

public T Value =&gt; _value!.Value;

public bool Specified =&gt; _value is not null;

public override string ToString() =&gt; _value is null ? &quot;&lt;NONE&gt;&quot; : _value.ToString()!;

readonly T? _value;

}

You could use that to implement your CountingDevice class like so:

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
}

Usage is quite natural because of the implicit conversions:

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()); // &quot;&lt;NONE&gt;&quot;

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.

}

You might also want to use optional reference types, but to do that you will need to declare a generic with the class constraint:

public readonly struct OptionalRef<T> where T : class
{
public OptionalRef(T value)
{
_value = value;
}

public static implicit operator T(OptionalRef&lt;T&gt; opt) =&gt; opt.Value;
public static implicit operator OptionalRef&lt;T&gt;(T opt) =&gt; new(opt);

public T Value =&gt; _value ?? throw new InvalidOperationException(&quot;Accessing an unspecified value.&quot;);

public bool Specified =&gt; _value is not null;

public override string ToString() =&gt; _value is null ? &quot;&lt;NONE&gt;&quot; : _value.ToString()!;

readonly T? _value;

}

Personally, I think that's a bit overkill. I'd just use nullable value types, int?, double? etc, but it depends on the expected usage.

英文:

If you implement a generic solution and add implicit conversion operators, it's quite convenient to use.

Here's a sample Optional&lt;T&gt; struct (I made it a readonly struct to ensure immutable mechanics):

public readonly struct Optional&lt;T&gt; where T : struct
{
    public Optional(T value)
    {
        _value = value;
    }

    public static implicit operator T(Optional&lt;T&gt; opt) =&gt; opt.Value;
    public static implicit operator Optional&lt;T&gt;(T opt) =&gt; new(opt);

    public T Value =&gt; _value!.Value;

    public bool Specified =&gt; _value is not null;

    public override string ToString() =&gt; _value is null ? &quot;&lt;NONE&gt;&quot; : _value.ToString()!;

    readonly T? _value;
}

You could use that to implement your CountingDevice class like so:

public partial class CountingDevice
{
    public int              countingDeviceNo   { get; set; }
    public string           countingDeviceName { get; set; }
    public string           obis               { get; set; }
    public Optional&lt;int&gt;    integralPart       { get; set; }
    public Optional&lt;int&gt;    fractionalPart     { get; set; }
    public Optional&lt;double&gt; value              { get; set; }
    public Optional&lt;bool&gt;   offPeakFlag        { get; set; }
    // snipped for brevity
}

Usage is quite natural because of the implicit conversions:

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()); // &quot;&lt;NONE&gt;&quot;

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

You might also want to use optional reference types, but to do that you will need to declare a generic with the class constraint:

public readonly struct OptionalRef&lt;T&gt; where T : class
{
    public OptionalRef(T value)
    {
        _value = value;
    }

    public static implicit operator T(OptionalRef&lt;T&gt; opt) =&gt; opt.Value;
    public static implicit operator OptionalRef&lt;T&gt;(T opt) =&gt; new(opt);

    public T Value =&gt; _value ?? throw new InvalidOperationException(&quot;Accessing an unspecified value.&quot;);

    public bool Specified =&gt; _value is not null;

    public override string ToString() =&gt; _value is null ? &quot;&lt;NONE&gt;&quot; : _value.ToString()!;

    readonly T? _value;
}

Personally, I think that's a bit overkill. I'd just use nullable value types, int?, double? etc, but it depends on the expected usage.

答案4

得分: 1

另一个(昂贵)的解决方案是编写一个方法,该方法接受一个对象、属性名称和新属性值作为参数。然后,您可以使用反射来设置指定属性的属性值,以及搜索要设置的bool字段(如果存在的话)。

请注意,您需要传递正确的属性类型。例如,对于value属性,没有编译时检查,以确保您传递的是double而不是string

下面我创建了一个扩展方法,它适用于object类型,以简化在我们的主要代码中调用该方法(该方法成为对象本身的成员):

public static class Extensions
{
    // 需要:using System.Reflection;
    public static bool SetPropertyAndSpecified(this object obj, 
        string propertyName, object propertyValue)
    {
        // 参数验证由用户完成

        // 检查'obj'是否具有指定的'propertyName',如果有,则设置'propertyValue'
        PropertyInfo prop = obj.GetType().GetProperty(propertyName,
            BindingFlags.Public | BindingFlags.Instance);

        if (prop != null && prop.CanWrite)
        {
            prop.SetValue(obj, propertyValue, null);

            // 检查相关的"FieldSpecified"字段,并将其设置为'true'(如果存在)
            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();

    // 请注意,'valueFieldSpecified' 和 'integralPartFieldSpecified' 在 'counter' 上设置为 'false'

    // 调用我们的方法来设置一些属性
    counter.SetPropertyAndSpecified(nameof(counter.integralPart), 42);
    counter.SetPropertyAndSpecified(nameof(counter.value), 69d);

    // 现在 'valueFieldSpecified' 和 'integralPartFieldSpecified' 在 'counter' 上设置为 'true'
}
英文:

Another (expensive) solution would be to write a method that takes in an object, a property name, and the new property value. You can then use reflection to both set the property value for the specified property, as well as search for the bool field that you want to set (if it exists).

Note that you need to pass the correct type for the property. There's no compile-time checking that you're passing a double instead of a string for the value property, for example.

Below I've created an extension method on the object type to simplify calling the method in our main code (the method becomes a member of the object itself):

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 &#39;obj&#39; has specified &#39;propertyName&#39; 
        // and set &#39;propertyValue&#39; if it does
        PropertyInfo prop = obj.GetType().GetProperty(propertyName,
            BindingFlags.Public | BindingFlags.Instance);

        if (prop != null &amp;&amp; prop.CanWrite)
        {
            prop.SetValue(obj, propertyValue, null);

            // Check for related &quot;FieldSpecified&quot; field 
            // and set it to &#39;true&#39; if it exists
            obj.GetType().GetField($&quot;{propertyName}FieldSpecified&quot;,
                BindingFlags.Public | BindingFlags.Instance)?.SetValue(obj, true);

            return true;
        }

        return false;
    }
}

After you add this class to your project, you can do something like:

static void Main(string[] args)
{
    var counter = new CountingDevice();

    // Note that &#39;valueFieldSpecified&#39; and `integralPartFieldSpecified&#39; 
    // are set to &#39;false&#39; on &#39;counter&#39;

    // Call our method to set some properties
    counter.SetPropertyAndSpecified(nameof(counter.integralPart), 42);
    counter.SetPropertyAndSpecified(nameof(counter.value), 69d);

    // Now &#39;valueFieldSpecified&#39; and &#39;integralPartFieldSpecified&#39; 
    // are set to &#39;true&#39; on &#39;counter&#39;
}

答案5

得分: 0

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);
英文:

C# doesn't allow overloading the = operator (unlike eg C++). However, your suggestion C should work. It's a bit of a hassle, too, since you'll have to write a bunch of methods, but you could write an extension method such as


public static class Extensions
{
    public static void UpdateIntegralPart(this CountingDevice dev, double value)
    {
        dev.integralPart = value;
        dev.integralPartSpecified = true;
    }
}

Then you can call

   countingDevice.UpdateIntegralPart(1234);

huangapple
  • 本文由 发表于 2023年2月8日 21:02:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/75386213.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定