在 Get 访问器中生成结构值

Generate Struct Value in Get Accessor

TL:DR;创建一个结构是不是很糟糕,其中值在结构本身的 get 开头做一些初始化自身的事情(没有 public 属性,但任何 comparison/etc. 执行初始化),并且如果是,为什么?


我想知道以有效不可变的方式生成结构的有效默认值是多么糟糕的想法。我读过有一个可变结构有多糟糕等等,但是如果你有一个没有 public 属性的结构怎么办 - 它本身就是它代表的值 - 并且该值对应于一些外部不可变资源?

例如,考虑以下结构:

using System;
public struct Computer
{
    private string _name;
    private string _domain;
    private bool _isInitialized;

    public static Computer Parse(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }
        var result = new Computer();
        result._name = string.Copy(name);
        result._domain = string.Empty;
        result._isInitialized = true;
        return result;
    }
    public static Computer Parse(string name, string domain)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new ArgumentNullException("name");
        }
        var result = new Computer();
        result._name = string.Copy(name);
        if (_domain == null) { result._domain = string.Empty; }
        else { result._domain = string.Copy(domain); }
        result._isInitialized = true;
        return result;
    }
    private void Initialize()
    {
        if (!_isInitialized)
        {
            var source = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties();
            _name = source.HostName;
            _domain = source.DomainName;
            _isInitialized = true;
        }
    }
    public override string ToString()
    {
        Initialize();
        if (!string.IsNullOrEmpty(_domain)) {
            return _name + "." + _domain;
        }
        else {
            return _name;
        }
    }
    public override bool Equals(object other)
    {
        Initialize();
        if (other is Computer)
        {
            var otherComputer = (Computer)other;
            return _name.Equals(otherComputer._name, StringComparison.OrdinalIgnoreCase) &&
                _domain.Equals(otherComputer._domain, StringComparison.OrdinalIgnoreCase);
        }
        else 
        {
            return false;
        }
    }
    // additional comparison methods omitted.
}

如上所示,Computer 实体的任何比较等操作都将导致初始化 - 实际上,观察 Computer 将导致它不再是 0 字节值。

我为什么要这么做?我想要类似于不可变值类型的东西,默认情况下,它代表 actual 值——而不是真正默认的 0 字节结构。然后我可以使用这样的东西作为参数的默认值:public void DoSomething(Computer computer = default) 并且知道 default 表示本地设备 - 如果这不是 ValueType,我将不得不传入null 作为参数的默认值,因为不可能有常量引用类型(即不能有 public void DoSomething(string computerName = Environment.MachineName))。

这意味着 true“默认值”从未真正被观察到 - 并且该结构永远不会被读取为 0 字节值,我读过的是什么值类型的 default 本质上是。

为什么 不应该 我这样做 - 还是完全可以?在众所周知的代码中是否有应用这种做法的实例?

generally a bad idea 除了 return getter 中的值之外的任何操作。

  • 用户不希望 getter 抛出。
  • 用户不希望 getter 花费很多时间。
  • 用户不希望getter修改对象的内部状态。

关于结构是只读的,在较新版本的 C# 中你可以声明一个 struct to be read-only,这允许一些优化,并且更容易维护(结构是只读的事实是显而易见的).

这是 readonly struct Computer 的实现,其中 default(Computer) 的行为类似于 Computer.Local:

public readonly struct Computer
{
    // Use a singleton for the local computer.
    public static Computer Local { get; }

    static Computer()
    {
        var source = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties();
        Local = new Computer(source.HostName, source.DomainName);
    }

    private readonly string _name;
    private readonly string _domain;
    private readonly bool _isNotDefault;

    private Computer(string name)
    {
        _name = name ?? throw new ArgumentNullException(nameof(name));
        _domain = string.Empty;
        _isNotDefault = true;
    }

    private Computer(string name, string domain)
    {
        _name = name ?? throw new ArgumentNullException(nameof(name));
        _domain = domain ?? throw new ArgumentNullException(nameof(domain));
        _isNotDefault = true;
    }

    public static Computer Parse(string name) => new Computer(name);
    public static Computer Parse(string name, string domain) => new Computer(name, domain);

    public string Name => _isNotDefault ? _name : Local.Name;
    public string Domain => _isNotDefault ? _domain : Local.Domain;

    public override string ToString()
    {
        return string.IsNullOrEmpty(Domain) ? Name : Name + "." + Domain;
    }

    public override bool Equals(object other)
    {
        return other switch
        {
            Computer otherComputer => Name.Equals(otherComputer.Name, StringComparison.OrdinalIgnoreCase) &&
                                      Domain.Equals(otherComputer.Domain, StringComparison.OrdinalIgnoreCase),
            _ => false
        };
    }
}