什么时候应该定义在C#(显式或隐式)转换运营商?(When should I define a (e

2019-06-27 09:14发布

的C#一种略为鲜为人知的功能是创建隐式或显式的可能性的用户定义类型的转换 。 我一直在现在写了6年的C#代码,我从来没有使用过。 所以,我怕我可能会丢失良好的机遇。

什么是用户定义的转换的合法,良好的用途? 你有例子,他们不只是定义一个自定义的方法更好呢?

-

事实证明,微软有一些设计准则有关转换,其中最相关的是:

不提供转换操作符,如果这种转换没有明确由最终用户的预期。

但是,当转换“预期”? 玩具类数量之外,我想不出任何真实世界的使用案例。


下面是在答案中提供的示例的汇总:

  • 弧度/度/双
  • 极地/的Point2D
  • 开尔文/华氏/摄氏度

图案似乎是:隐式转换大多是有用的限定数值/值类型时,转换正由公式定义(仅?)。 现在回想起来,这是一种明显的。 不过,我不知道如果非数值类也可以从隐式转换中获益..?

Answer 1:

作为评价所述,度和旋转是一个很好的例子,以避免混淆双值,特别是API之间。

我拉出RadiansDegrees ,我们目前正在使用的类,在这里他们。 现在考虑看看他们(经过这么长时间)我要清除它们(尤其是评论/文档),并确保他们是很好的测试。 值得庆幸的是,我已经成功地得到时间的调度这样做。 无论如何,使用这些在你自己的风险,我不能保证如果在这里所有的数学是正确的,因为我敢肯定,我们还没有实际使用/测试的所有我们写的功能。

弧度

/// <summary>
/// Defines an angle in Radians
/// </summary>
public struct Radians
{
    public static readonly Radians ZERO_PI = 0;
    public static readonly Radians ONE_PI = System.Math.PI;
    public static readonly Radians TWO_PI = ONE_PI * 2;
    public static readonly Radians HALF_PI = ONE_PI * 0.5;
    public static readonly Radians QUARTER_PI = ONE_PI * 0.25;

    #region Public Members

    /// <summary>
    /// Angle value
    /// </summary>
    public double Value;
    /// <summary>
    /// Finds the Cosine of the angle
    /// </summary>
    public double Cos
    {
        get
        {
            return System.Math.Cos(this);
        }
    }
    /// <summary>
    /// Finds the Sine of the angle
    /// </summary>
    public double Sin
    {
        get
        {
            return System.Math.Sin(this);
        }
    }

    #endregion

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="value">angle value in radians</param>
    public Radians(double value)
    {
        this.Value = value;
    }
    /// <summary>
    /// Gets the angle in degrees
    /// </summary>
    /// <returns>Returns the angle in degrees</returns>
    public Degrees GetDegrees()
    {
        return this;
    }

    public Radians Reduce()
    {
        double radian = this.Value;
        bool IsNegative = radian < 0;
        radian = System.Math.Abs(radian);
        while (radian >= System.Math.PI * 2)
        {
            radian -= System.Math.PI * 2;
        }
        if (IsNegative && radian != 0)
        {
            radian = System.Math.PI * 2 - radian;
        }
        return radian;
    }

    #region operator overloading

    /// <summary>
    /// Conversion of Degrees to Radians
    /// </summary>
    /// <param name="deg"></param>
    /// <returns></returns>
    public static implicit operator Radians(Degrees deg)
    {
        return new Radians(deg.Value * System.Math.PI / 180);
    }
    /// <summary>
    /// Conversion of integer to Radians
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    public static implicit operator Radians(int i)
    {
        return new Radians((double)i);
    }
    /// <summary>
    /// Conversion of float to Radians
    /// </summary>
    /// <param name="f"></param>
    /// <returns></returns>
    public static implicit operator Radians(float f)
    {
        return new Radians((double)f);
    }
    /// <summary>
    /// Conversion of double to Radians
    /// </summary>
    /// <param name="dbl"></param>
    /// <returns></returns>
    public static implicit operator Radians(double dbl)
    {
        return new Radians(dbl);
    }
    /// <summary>
    /// Conversion of Radians to double
    /// </summary>
    /// <param name="rad"></param>
    /// <returns></returns>
    public static implicit operator double(Radians rad)
    {
        return rad.Value;
    }
    /// <summary>
    /// Add Radians and a double
    /// </summary>
    /// <param name="rad"></param>
    /// <param name="dbl"></param>
    /// <returns></returns>
    public static Radians operator +(Radians rad, double dbl)
    {
        return new Radians(rad.Value + dbl);
    }
    /// <summary>
    /// Add Radians to Radians
    /// </summary>
    /// <param name="rad1"></param>
    /// <param name="rad2"></param>
    /// <returns></returns>
    public static Radians operator +(Radians rad1, Radians rad2)
    {
        return new Radians(rad1.Value + rad2.Value);
    }
    /// <summary>
    /// Add Radians and Degrees
    /// </summary>
    /// <param name="rad"></param>
    /// <param name="deg"></param>
    /// <returns></returns>
    public static Radians operator +(Radians rad, Degrees deg)
    {
        return new Radians(rad.Value + deg.GetRadians().Value);
    }
    /// <summary>
    /// Sets Radians value negative
    /// </summary>
    /// <param name="rad"></param>
    /// <returns></returns>
    public static Radians operator -(Radians rad)
    {
        return new Radians(-rad.Value);
    }
    /// <summary>
    /// Subtracts a double from Radians
    /// </summary>
    /// <param name="rad"></param>
    /// <param name="dbl"></param>
    /// <returns></returns>
    public static Radians operator -(Radians rad, double dbl)
    {
        return new Radians(rad.Value - dbl);
    }
    /// <summary>
    /// Subtracts Radians from Radians
    /// </summary>
    /// <param name="rad1"></param>
    /// <param name="rad2"></param>
    /// <returns></returns>
    public static Radians operator -(Radians rad1, Radians rad2)
    {
        return new Radians(rad1.Value - rad2.Value);
    }
    /// <summary>
    /// Subtracts Degrees from Radians
    /// </summary>
    /// <param name="rad"></param>
    /// <param name="deg"></param>
    /// <returns></returns>
    public static Radians operator -(Radians rad, Degrees deg)
    {
        return new Radians(rad.Value - deg.GetRadians().Value);
    }


    #endregion

    public override string ToString()
    {
        return String.Format("{0}", this.Value);
    }

    public static Radians Convert(object value)
    {
        if (value is Radians)
            return (Radians)value;
        if (value is Degrees)
            return (Degrees)value;

        return System.Convert.ToDouble(value);
    }
}

学位

public struct Degrees
{
    public double Value;       

    public Degrees(double value) { this.Value = value; }

    public Radians GetRadians()
    {
        return this;
    }

    public Degrees Reduce()
    {
        return this.GetRadians().Reduce();
    }

    public double Cos
    {
        get
        {
            return System.Math.Cos(this.GetRadians());
        }
    }

    public double Sin
    {
        get
        {
            return System.Math.Sin(this.GetRadians());
        }
    }

    #region operator overloading

    public static implicit operator Degrees(Radians rad)
    {
        return new Degrees(rad.Value * 180 / System.Math.PI);
    }

    public static implicit operator Degrees(int i)
    {
        return new Degrees((double)i);
    }

    public static implicit operator Degrees(float f)
    {
        return new Degrees((double)f);
    }

    public static implicit operator Degrees(double d)
    {
        return new Degrees(d);
    }

    public static implicit operator double(Degrees deg)
    {
        return deg.Value;
    }

    public static Degrees operator +(Degrees deg, int i)
    {
        return new Degrees(deg.Value + i);
    }

    public static Degrees operator +(Degrees deg, double dbl)
    {
        return new Degrees(deg.Value + dbl);
    }

    public static Degrees operator +(Degrees deg1, Degrees deg2)
    {
        return new Degrees(deg1.Value + deg2.Value);
    }

    public static Degrees operator +(Degrees deg, Radians rad)
    {
        return new Degrees(deg.Value + rad.GetDegrees().Value);
    }

    public static Degrees operator -(Degrees deg)
    {
        return new Degrees(-deg.Value);
    }

    public static Degrees operator -(Degrees deg, int i)
    {
        return new Degrees(deg.Value - i);
    }

    public static Degrees operator -(Degrees deg, double dbl)
    {
        return new Degrees(deg.Value - dbl);
    }

    public static Degrees operator -(Degrees deg1, Degrees deg2)
    {
        return new Degrees(deg1.Value - deg2.Value);
    }

    public static Degrees operator -(Degrees deg, Radians rad)
    {
        return new Degrees(deg.Value - rad.GetDegrees().Value);
    }

    #endregion

    public override string ToString()
    {
        return String.Format("{0}", this.Value);
    }

    public static Degrees Convert(object value)
    {
        if (value is Degrees)
            return (Degrees)value;
        if (value is Radians)
            return (Radians)value;

        return System.Convert.ToDouble(value);
    }
}

一些使用范例

正在使用的API时,这些真正受益。 同时,在内部,您的组织可能决定严格遵守度弧度坚持以避免mixups,至少这些类,你可以使用最有意义的类型。 例如,公开消耗API或GUI的API可以使用Degrees ,而你的数学重/触发或内部使用可能使用Radians 。 考虑下面的类/打印功能:

public class MyRadiansShape
{
    public Radians Rotation { get; set; }
}

public class MyDegreesShape
{
    public Degrees Rotation { get; set; }
}

public static void PrintRotation(Degrees degrees, Radians radians)
{
    Console.WriteLine(String.Format("Degrees: {0}, Radians: {1}", degrees.Value, radians.Value));
}

是啊,代码是相当做作(和可怕的暧昧),不过没关系! 只是表明它如何能帮助减少意外mixups。

var radiansShape = new MyRadiansShape() { Rotation = Math.PI / 2}; //prefer "Radians.HALF_PI" instead, but just as an example
var degreesShape = new MyDegreesShape() { Rotation = 90 };

PrintRotation(radiansShape.Rotation, radiansShape.Rotation);
PrintRotation(degreesShape.Rotation, degreesShape.Rotation);
PrintRotation(radiansShape.Rotation + degreesShape.Rotation, radiansShape.Rotation + degreesShape.Rotation);

//Degrees: 90, Radians: 1.5707963267949
//Degrees: 90, Radians: 1.5707963267949
//Degrees: 180, Radians: 3.14159265358979

然后,他们可以为实现基于角度,诸如极坐标数学概念真的有用:

double distance = 5;
Polar polarCoordinate = new Polar(distance, (degreesShape.Rotation - radiansShape.Rotation) + Radians.QUARTER_PI);
Console.WriteLine("Polar Coordinate Angle: " + (Degrees)polarCoordinate.Angle); //because it's easier to read degrees!
//Polar Coordinate Angle: 45

最后,您可以实现Point2D类(或使用System.Windows.Point)与隐式转换为/ Polar

Point2D cartesianCoordinate = polarCoordinate;
Console.WriteLine(cartesianCoordinate.X + ", " + cartesianCoordinate.Y);
//3.53553390593274, 3.53553390593274

正如我所说的,我想利用另一路经这些类,并可能消除double隐式转换为Radians ,避免一对夫妇的角落情况下mixups和编译器的含糊不清有是可能的。 这些实际上有我们创建的静态前ONE_PIHALF_PI (等)领域,我们从一些多被转换Math.PI双。

编辑:这里的Polar类作为额外的隐式转换的示范。 它采用的优点Radians类(因而其隐式转换)和它的辅助方法和Point2D类。 我在这里没有包括在内,但在Polar类可以很容易地实现与运营商交互Point2D类,但这些都没有相关的讨论。

public struct Polar
{
    public double Radius;
    public Radians Angle;

    public double X { get { return Radius * Angle.Cos; } }
    public double Y { get { return Radius * Angle.Sin; } }

    public Polar(double radius, Radians angle)
    {
        this.Radius = radius;
        this.Angle = angle;
    }

    public Polar(Point2D point)
        : this(point.Magnitude(), point.GetAngleFromOrigin())
    {
    }

    public Polar(Point2D point, double radius)
        : this(radius, point.GetAngleFromOrigin())
    {
    }

    public Polar(Point2D point, Point2D origin)
        : this(point - origin)
    {
    }

    public Point2D ToCartesian()
    {
        return new Point2D(X, Y);
    }

    public static implicit operator Point2D(Polar polar)
    {
        return polar.ToCartesian();
    }

    public static implicit operator Polar(Point2D vector)
    {
        return new Polar(vector);
    }
}


Answer 2:

当有一个自然而清晰的转换或从不同类型的可以使用转换操作符。

例如说你有一个代表温度的数据类型:

public enum TemperatureScale { Kelvin, Farenheit, Celsius }

public struct Temperature {

  private TemperatureScale _scale;
  private double _temp;

  public Temperature(double temp, TemperatureScale scale) {
    _scale = scale;
    _temp = temp;
  }

  public static implicit operator Temperature(double temp) {
    return new Temperature(temp, TemperatureScale.Kelvin);
  }

}

使用隐式的操作,你可以指定一个双重的温度变化,它会自动被用作开尔文:

Temperature a = new Temperature(100, TemperatureScale.Celcius);
Temperature b = 373.15; // Kelvin is default


Answer 3:

我用它来有从无缝转换DateTime"yyyyMMdd"或到其相应的int (YYYYMMDD)值。

例如:

void f1(int yyyyMMdd);
void f2(string yyyyMMdd);

...
f1(30.YearsFrom(DateTime.Today));
f2(30.YearsFrom(DateTime.Today));

...
public static DateAsYyyyMmDd YearsFrom(this int y, DateTime d) 
{
    return new DateAsYyyyMmDd(d.AddYears(y));
}

...
public class DateAsYyyyMmDd
{
    private readonly DateTime date;

    public DateAsYyyyMmDd(DateTime date)
    {
        this.date = date;
    }

    public static implicit operator int(DateOrYyyyMmDd d)
    {
        return Convert.ToInt32(d.date.ToString("yyyyMMdd"));
    }

    public static implicit operator string(DateOrYyyyMmDd d)
    {
        return d.date.ToString("yyyyMMdd");
    }
}


Answer 4:

假设你有一个类产品(如玩具),您正在使用的应用程序商店:

class Product
{
    string name;
    decimal price;
    string maker;
    //etc...
}

您可以定义一个明确的转换,可能做到以下几点:

public static explicit operator string(Product p)
{
    return "Product Name: " + p.name + " Price: " + p.price.ToString("C") + " Maker: " + p.maker;
    // Or you might just want to return the name.
}

当你做这样的事情的方式:

textBox1.Text = (string)myProduct;

这将格式化输出到什么在为明确经营者Product类。


不提供转换操作符,如果这种转换没有明确由最终用户的预期。

什么微软指这是如果提供一个转换操作符,你不回非预期的结果。 使用我们的最后一个例子Product类,这是后话,将返回一个非预期的结果:

public static explicit operator string(Product p)
{
    return (p.price * 100).ToString();
    //...
}

显然,没有人会真正做到这一点,但如果别人是用Product类别和使用一个明确的字符串转换,他们会不希望它返回的价格乘以100。

希望这可以帮助!



Answer 5:

一般来说,如果两件事情在逻辑上是可兑换。 我使用他们在这种情况下,提供更流畅的代码。 我有时也用它们来解决语言功能不太工作,我希望他们的方式。

下面是说明过去的想法是类似的东西我已经在生产中使用非常简单的,人为的例子...

class Program
{. 
    static void Main(string[] args)
    {
        Code code1 = new Code { Id = 1, Description = "Hi" };
        Code code2 = new Code { Id = 2, Description = "There" };

        switch (code1)
        {
            case 23: 
              // do some stuff
              break;
            // other cases...
        }
    }
}

public class Code
{
    private int id;
    private string description;

    public int Id { get; set; }
    public string Description { get; set; }

    public static implicit operator int(Code code)
    {
        return code.Id;
    }
}


文章来源: When should I define a (explicit or implicit) conversion operator in C#?