首页 > 解决方案 > 如何设计表示既不是空也不是空格的字符串的类型

问题描述

很多时候,我们在业务实体的构造函数中传递字符串,我们希望确保这些字符串实际上带有一个值。在这种情况下,我们对构造函数参数进行验证,并且ArgumentException每次传入的字符串为空或空格时都会抛出一个。

这是我的意思的一个例子:

public class Person 
{
   public string Name { get; }

   public Person(string name)
   {
      if (string.IsNullOrWhiteSpace(name))
      {
         throw new ArgumentException("A person name cannot be null or white space", nameof(name));
      }

      this.Name = name;
   }
}

厌倦了重复自己,我决定设计一种自动安全类型,表示既不是空也不是空格的字符串。这样我可以直接在我的业务代码中使用该类型的实例并避免任何验证,因为该类型的每个实例都是自动安全的(换句话说,验证代码现在在一个地方,即类型本身的代码)。

这是NonEmptyString结构(原始代码在这里):

using System;

namespace Deltatre.Utils.Types
{
  /// <summary>
  /// This type wraps a string which is guaranteed to be neither null nor white space
  /// </summary>
  public struct NonEmptyString
  {
    /// <summary>
    /// Implicit conversion from <see cref="NonEmptyString"/> to <see cref="string"/>
    /// </summary>
    /// <param name="nonEmptyString">The instance of <see cref="NonEmptyString"/> to be converted</param>
    public static implicit operator string(NonEmptyString nonEmptyString)
    {
      return nonEmptyString.Value;
    }

    /// <summary>
    /// Explicit conversion from <see cref="string"/> to <see cref="NonEmptyString"/>
    /// </summary>
    /// <param name="value">The instance of <see cref="string"/> to be converted</param>
    /// <exception cref="InvalidCastException">Throws <see cref="InvalidCastException"/> when <paramref name="value"/> is null or white space</exception>
    public static explicit operator NonEmptyString(string value)
    {
      try
      {
        return new NonEmptyString(value);
      }
      catch (ArgumentException ex)
      {
        throw new InvalidCastException($"Unable to convert the provided string to {typeof(NonEmptyString).Name}", ex);
      }
    }

    /// <summary>
    /// Creates new instance of <see cref="NonEmptyString"/>
    /// </summary>
    /// <param name="value">The string to be wrapped</param>
    /// <exception cref="ArgumentException">Throws <see cref="ArgumentException"/> when parameter <paramref name="value"/> is null or white space</exception>
    public NonEmptyString(string value)
    {
      if (string.IsNullOrWhiteSpace(value))
        throw new ArgumentException($"Parameter {nameof(value)} cannot be null or white space", nameof(value));

      this.Value = value;
    }

    /// <summary>
    /// Gets the wrapped string
    /// </summary>
    public string Value { get; }

    /// <summary>Indicates whether this instance and a specified object are equal.</summary>
    /// <param name="obj">The object to compare with the current instance. </param>
    /// <returns>
    ///     <see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, <see langword="false" />. </returns>
    public override bool Equals(object obj)
    {
      if (!(obj is NonEmptyString))
      {
        return false;
      }

      var other = (NonEmptyString)obj;
      return this.Value == other.Value;
    }

    /// <summary>Returns the hash code for this instance.</summary>
    /// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
    public override int GetHashCode()
    {
      unchecked
      {
        int hash = 17;
        hash = (hash * 23) + (this.Value == null ? 0 : this.Value.GetHashCode());
        return hash;
      }
    }

    /// <summary>
    /// Compares two instances of <see cref="NonEmptyString"/> for equality
    /// </summary>
    /// <param name="left">An instance of <see cref="NonEmptyString"/></param>
    /// <param name="right">An instance of <see cref="NonEmptyString"/></param>
    /// <returns></returns>
    public static bool operator ==(NonEmptyString left, NonEmptyString right)
    {
      return left.Equals(right);
    }

    /// <summary>
    /// Compares two instances of <see cref="NonEmptyString"/> for inequality
    /// </summary>
    /// <param name="left">An instance of <see cref="NonEmptyString"/></param>
    /// <param name="right">An instance of <see cref="NonEmptyString"/></param>
    /// <returns></returns>
    public static bool operator !=(NonEmptyString left, NonEmptyString right)
    {
      return !(left == right);
    }
  }
}

使用我的新类型,我可以通过这种方式更改以前的代码:

public class Person 
{
  public NonEmptyString Name { get; }

  public Person(NonEmptyString name)
  {          
    this.Name = name;
  }
}

这种设计的唯一问题是默认构造函数表示,它始终可用,因为我的类型是struct.

如果有人使用我的代码编写var myString = new NonEmptyString();,他会得到一个封装null引用的类型的实例:这是我想避免的,因为这样做会使我的自动安全类型的整个目的无效。换句话说,我不想依赖程序员不调用默认构造函数,我想让它不可能误用这种类型。

我想出了几个想法:

你有什么建议吗?你更喜欢什么方法?

标签: c#.netstringstructc#-7.0

解决方案


您可以使用支持字段_value并为属性实现 getter,Value而不是使用自动属性。喜欢

public string Value => _value ?? "";

然后让每个函数在_value为空时工作;


推荐阅读