在 Get 访问器中生成结构值

问题描述

TL:DR;制作一个结构体,其中值在 get 的开头为结构体本身进行初始化(没有公共属性,但任何比较/等都执行初始化),如果是这样,为什么?>


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

例如,考虑以下结构:

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 字节值。>

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

这意味着 true认值”从未真正被观察到 - 并且该结构永远不能被读取为 0 字节值,我读到的是值类型的 {{1 }} 本质上是。

为什么不应该我这样做 - 或者完全没问题?在众所周知的代码中是否有应用这种做法的实例?

解决方法

Is 是 generally a bad idea,除了在 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
        };
    }
}