【问题标题】:Generate Struct Value in Get Accessor在获取访问器中生成结构值
【发布时间】:2021-06-11 03:09:17
【问题描述】:

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

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

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

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

【问题讨论】:

  • 我认为你所做的只是the singleton pattern。这是很常见的。在后来的几年里,它不再那么流行了。
  • 我可以问你为什么首先使用结构吗? _value 是一个字符串 = System.String 是一个引用类型。您的代码中有更多奇怪的怪癖,例如初始化程序的“默认 null”参数(这可能是 hack?)。
  • @jhbonarius 也许 ComputerName 会是一个更好的例子。我可以编写一百个在“计算机”上运行的方法,并且需要一个 ComputerName 参数,我希望它不仅仅是一个字符串。通过提供default(而不是“.”、“localhost”等),可以表明该方法应该针对本地设备——但您也可以使用ComputerName.Parse(string) 来引用另一台计算机。这增加了对默认值行为的澄清和简化。 Local 单例可能会产生误导;这只是一个缓存的默认值,很像CancellationToken.None
  • 好吧,为什么不是一个只有只读字符串参数的简单类呢?或者 a record: record Computer(string Name); var myComputer = new Computer("MyName");。你为什么想要一个单例(单个静态实例)?
  • 针对后一个问题,很多结构都包含引用类型成员。该结构代表一个服务标签,因此是它自己的不同实体,但数据存储为字符串。如果这是不好的做法,我想了解原因——我仍然有很多不明白的地方。 Null CimSession 将使用本地会话 - 在执行计算机上运行。我想我给这个参数一个默认值只是为了更快的测试。

标签: c# struct value-type


【解决方案1】:

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

【讨论】:

  • 在这种情况下,我不能提供 Computer.Local 作为方法的默认参数值,但是 - 我可以吗?一个目标是 default(Computer) 将是一个指示本地设备的值,以便我可以定义一个方法 DoSomethingToComputer(Computer computer = default),因此默认 = 本地计算机。您关于吸气剂不等待/投掷的观点是有道理的。我想我的这个模型是基于一个懒惰地构造一个字段的吸气剂——比如public string MyString {get => _string ??= "initial"; } private string _string;
  • @Stroniax 仔细阅读,在我的代码中default(Computer).Name 返回本地计算机的名称。
  • 不,恕我直言,您概括得太多了。例如,您正在谈论“用户”,而还没有某个用户(除了您自己)。并定义“很多时间”。一切视情况而定。
  • @JHBonarius,请参阅 stackoverflow.com/a/1294189/361177 了解有关 getter 的一些指南(其中 a lot of time 定义为 db/network 调用)。事实上,所有这些都是基于stackoverflow.com/questions/2784934/… 的意见,并且该语言允许您抛出、花时间、修改 getter 中的内部状态。您可以这样做,但如果您在团队中工作或公开 API(即:如果您的代码有用户),最好有一些约定并尊重他们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-30
  • 1970-01-01
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 2012-01-05
  • 1970-01-01
相关资源
最近更新 更多