New type definition in C#

匿名 (未验证) 提交于 2019-12-03 08:36:05

问题:

I am looking for possibilities to define a new type and using it in C# like below:

Class definition:

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 }

Usage:

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

回答1:

A type may be overkill, but if you want one, this is a good start:

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

Of course, there are many more interfaces to implement, for example IConvertible and IComparable<Double180> would be nice.

As you can see, you know where this starts, but you don't know where it ends.

A setter validator, as suggested by the other answers, might be a better idea.



回答2:

You don't necessarily need a new type for this. Instead of using an auto property, you can manually write a setter which validates the value:

public double Latitude {     get     {         return mLatitude;     }      set     {         if (value > 90 || value < -90)         {             throw new ArgumentOutOfRangeException("Invalid latitude");         }          mLatitude = value;     } }  private double mLatitude;

If you want to reuse this code, you could define your own type and use the above setter in it; then provide an appropriate constructor and conversion operators.



回答3:

You would probably be better adding System.ComponentModel.DataAnnotations and using [Range] like so:

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


回答4:

Use a double and have the setter check the value:

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


回答5:

Add a validation step to the 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;   } }

Note that you as you are providing an implementation of the property you will need to add a member variable to store the underlying property value.



回答6:

I basically got the idea: validation the input inside the setter. When it comes to type definition, it seems Structs are simply the best. So finally, I will use below in my project.

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

Then I will use like this

var position = new Coordinate(46.32, 34.23);

Thank you all for your valuable comments.



回答7:

I like the documentation to be the part of a system:

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

All dependent modules must be tested to comply with the specification of their dependency. Test-driven development is one way. Contract-driven development is another.

If you insist on "defencive programming" with run-time checks of values, then simply use a constructor:

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

Or use a 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 };         }     } }

Usage:

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

Summary:

  • Run-time checks ("defensive programming"):
    • Public setter with check (see other answers)
    • Public constructor with check
    • "Builder pattern" with builder performing checks
  • Test-time checks:
    • Test-driven
    • Contract-driven


转载请标明出处:New type definition in C#
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!