【问题标题】:New type definition in C#C# 中的新类型定义
【发布时间】:2014-03-02 06:29:48
【问题描述】:

我正在寻找定义新类型并在 C# 中使用它的可能性,如下所示:

类定义:

public class Position
{
    public double180 Longitude { get; set; } // double180 is a type within a range -180 and 180
    public double90 Latitude { get; set; } // double90 is a type within a range of -90 and 90
}

用法:

var position = new Position
{
     Longitude = 45,
     Latitude = 96 // This line should give an error while initializing the object
};

【问题讨论】:

  • 您可以使属性在其设置器中验证,或者您可以使用隐式转换创建一个包装 double 的类型。
  • @dvnrrs 提供的解决方案是对的,如果使用超出范围的值,你只需要定义你需要什么类型的错误。
  • 您是真的想创建一个新类型还是只是验证一些值?

标签: c# types


【解决方案1】:

您不一定需要新的类型。您可以手动编写一个验证值的 setter,而不是使用 auto 属性:

public double Latitude
{
    get
    {
        return mLatitude;
    }

    set
    {
        if (value > 90 || value < -90)
        {
            throw new ArgumentOutOfRangeException("Invalid latitude");
        }

        mLatitude = value;
    }
}

private double mLatitude;

如果你想重用这段代码,你可以定义你自己的类型并在里面使用上面的setter;然后提供适当的构造函数和转换运算符。

【讨论】:

    【解决方案2】:

    使用 double 并让 setter 检查值:

    private double _longitude;
    public double Longitude
    {
        get
        {
            return _longitude;
        }
        set
        {
            if(value < -180 || value > 180)
            {
                throw new ArgumentException("value");
            }
            _longitude = value;
        }
    }
    

    【讨论】:

      【解决方案3】:

      向 setter 添加验证步骤:

      private double m_Latitude;
      
      public double Latitude
      {
        get{return m_Latitude;}
      
        set
        {
          if(value < -90 || value > 90) throw new ArgumentException("value");
      
          m_Latitude = value;
        }
      }
      

      请注意,当您提供属性的实现时,您需要添加一个成员变量来存储基础属性值。

      【讨论】:

        【解决方案4】:

        您可能会更好地添加 System.ComponentModel.DataAnnotations 并像这样使用 [Range]:

        public class Position
        {
            [Range(-180, 180)]
            public double Longitude { get; set; }
        
            [Range(-90, 90)]
            public double Latitude { get; set; }
        }
        

        【讨论】:

        • system.componentmodel.dataannotations.rangeattribute 不是特定于 ASP.NET 的吗?
        • 这些属性仅供参考,并非由编译器或 CLR 严格执行。 (正如 DougM 指出的那样,特定领域。)
        • 属性可用于其他“领域”,如 WPF、动态控件等。
        • @FrankO 同意,但这仍然只是 advisory 验证,由调用者而不是被调用者强制执行。
        【解决方案5】:

        一个类型可能有点矫枉过正,但如果你想要一个,这是一个好的开始:

        struct Double180 : IEquatable<Double180>
        {
            private readonly double value;
        
            public Double180(double d)
            {
                if (d < -180 || d > 180)
                {
                    throw new ArgumentOutOfRangeException("d");
                }
        
                this.value = d;
            }
        
            public static implicit operator double(Double180 d)
            {
                return d.value;
            }
        
            public static explicit operator Double180(double d)
            {
                return new Double180(d);
            }
        
            public override string ToString()
            {
                return this.value.ToString();
            }
        
            public bool Equals(Double180 other)
            {
                return this.value == other.value;
            }
        
            public override bool Equals(object obj)
            {
                return obj is Double180 && this.Equals((Double180)obj);
            }
        
            public override int GetHashCode()
            {
                return this.value.GetHashCode();
            }
        
            public static bool operator ==(Double180 a, Double180 b)
            {
                return a.Equals(b);
            }
        
            public static bool operator !=(Double180 a, Double180 b)
            {
                return !a.Equals(b);
            }
        }
        

        当然,还有很多接口要实现,比如IConvertibleIComparable&lt;Double180&gt;就好了。

        如您所见,您知道这从哪里开始,但您不知道它在哪里结束。

        正如其他答案所建议的那样,设置器验证器可能是一个更好的主意。

        【讨论】:

        • 如果你要走模板路线,为什么不也模板double 部分?从我所看到的关键信息来看,关键信息是 -180 ..180 和 -90 .. 90,而不是保存这些值的数据类型。在某些情况下 IE 浮点数或整数也可以。
        • @PeterM 这是 C#。 C++ 模板不可用。
        • 我最近看 C++ 太多了。我的意思是让类型成为泛型类。
        • @PeterM 泛型对于这种事情来说太有限了,因为你不能使用运算符。虽然我的答案中的结构没有显示它,但您希望实现算术和比较运算符。此外,与 C++ 模板不同,泛型不能参数化范围约束。
        【解决方案6】:

        我基本上明白了:验证 setter 中的输入。在类型定义方面,Struct 似乎是最好的。所以最后,我将在我的项目中使用下面。

        public struct Coordinate
        {
            private readonly double _x;
            private readonly double _y;
        
            /// <summary>
            /// Longitude
            /// </summary>
            public double X
            {
                get { return _x; }
            }
        
            /// <summary>
            /// Latitude
            /// </summary>
            public double Y
            {
                get { return _y; }
            }
        
            /// <summary>
            /// Initiates a new coordinate.
            /// </summary>
            /// <param name="x">Longitude [-180, 180]</param>
            /// <param name="y">Latitude [-90, 90]</param>
            public Coordinate(double x, double y)
            {
                if (x < -180 || x > 180)
                    throw new ArgumentOutOfRangeException(
                        "x", "Longitude value must be in range of -180 and 180.");
        
                if (y < -90 || y > 90)
                    throw new ArgumentOutOfRangeException(
                        "y", "Latitude value must be in range of -90 and 90.");
        
                _x = x;
                _y = y;
            }
        }
        

        那我就这样用

        var position = new Coordinate(46.32, 34.23);
        

        感谢大家的宝贵cmets。

        【讨论】:

          【解决方案7】:

          我喜欢将文档作为系统的一部分:

          public class Position
          {
              /// <summary>
              /// ...
              /// 
              /// A value within a range -180 and 180
              /// </summary>
              public double Longitude { get; set; }
          
              /// <summary>
              /// ...
              /// 
              /// A value within a range -90 and 180
              /// </summary>
              public double Latitude { get; set; }
          }
          

          必须测试所有依赖模块以符合它们的依赖规范。 测试驱动开发是一种方式。 合同驱动开发是另一种情况。

          如果您坚持对值进行运行时检查的“防御性编程”,那么只需使用构造函数

          public class Position
          {
              /// <summary>
              /// ...
              /// 
              /// A value within a range -180 and 180
              /// </summary>
              public double Longitude { get; private set; }
          
              /// <summary>
              /// ...
              /// 
              /// A value within a range -90 and 180
              /// </summary>
              public double Latitude { get; private set; }
          
              public Position(double longitude, double latitude)
              {
                  if (longitude < -180 || longitude > 180)
                  {
                      throw new ArgumentOutOfRangeException();
                  }
          
                  if (latitude < -90 || latitude > 90)
                  {
                      throw new ArgumentOutOfRangeException();
                  }
          
                  Longitude = longitude;
          
                  Latitude = latitude;
              }
          }
          

          或者使用builder

          public class Position
          {
              public double Longitude { get; private set; }
          
              public double Latitude { get; private set; }
          
              /// <summary>
              /// Protects from invalid positions. Use <see cref="Position.Builder"/>
              /// </summary>
              private Position() { }
          
              /// <summary>
              /// Builds valid positions
              /// </summary>
              public class Builder
              {
                  public double Longitude { get; set; }
          
                  public double Latitude { get; set; }
          
                  public Position Build()
                  {
                      if (Longitude < -180 || Longitude > 180)
                      {
                          throw new ArgumentOutOfRangeException();
                      }
          
                      if (Latitude < -90 || Latitude > 90)
                      {
                          throw new ArgumentOutOfRangeException();
                      }
          
                      return new Position() { Latitude = this.Latitude, Longitude = this.Longitude };
                  }
              }
          }
          

          用法:

          Position p = new Position.Builder()
          {
              Latitude = 2,
              Longitude = 5
          }.Build();
          

          总结:

          • 运行时检查(“防御性编程”):
            • 带支票的公共二传手(见其他答案)
            • 带检查的公共构造函数
            • 构建器执行检查的“构建器模式”
          • 测试时间检查:
            • 测试驱动
            • 合同驱动

          【讨论】:

            猜你喜欢
            • 2019-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-09-27
            • 1970-01-01
            • 2011-03-13
            • 2012-03-07
            • 2012-03-24
            相关资源
            最近更新 更多