我应该何时在C#中定义(显式或隐式)转换运算符? [英] When should I define a (explicit or implicit) conversion operator in C#?

查看:395
本文介绍了我应该何时在C#中定义(显式或隐式)转换运算符?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

C#的一个鲜为人知的功能是可以创建隐式或显式用户定义的类型转换
我已经写了6年的C#代码,我从来没有使用它。因此,恐怕我可能会错失良好的机会。

A somewhat little-known feature of C# is the possibility to create implicit or explicit user-defined type conversions. I have been writing C# code for 6 years now, and I have never used it. So, I'm afraid I might be missing good opportunities.

什么是合法,良好的用户定义转换?你有没有比定义一个自定义方法更好的例子?

What are legitimate, good uses of user-defined conversions? Do you have examples where they are better than just defining a custom method?

-

Microsoft有关于转化的一些设计准则,其中最相关的是:

Turns out, Microsoft has some design guidelines about conversions, the most relevant of which is:


如果此类转换不是最终用户预期的
,请不要提供转换运算符。

Do not provide a conversion operator if such conversion is not clearly expected by the end users.

但是什么时候转换期望?在玩具号课程之外,我无法找出任何真实的用例。

But when is a conversion "expected"? Outside of toy number classes, I can't figure out any real-world use case.

在答案中提供的示例:


  • 半径/度/双

  • Polar / Point2D

  • Kelvin / Farenheit / Celsius

模式似乎是:隐式转换大多数在定义数值/值类型时非常有用,转换由公式定义。回想起来这是显而易见的。仍然,我不知道非数字类是否也可以受益于隐式转换。?

The pattern seems to be: implicit conversions are mostly (only?) useful when defining numerical/value types, the conversion being defined by a formula. In retrospect this is kind of obvious. Still, I wonder if non-numerical classes could also benefit from implicit conversions..?

推荐答案

我拔出了 Radians ,这是一个很好的例子,和 Degrees 我们目前使用的类和这里。现在看看他们(过了这么久)我想清理他们(特别是评论/文档),并确保他们被正确测试。幸运的是,我已经设法得到时间在调度这样做。无论如何,使用这些风险自担风险,我不能保证如果所有这里的数学是正确的,因为我很确定我们没有实际使用/测试

I pulled out the Radians and Degrees classes we're currently using and here they are. Taking a look at them now (after so long) I want to clean them up (especially the comments/documentation) and make sure they're properly tested. Thankfully, I've managed to get time in the scheduling to do so. At any rate, use these at your own risk, I can't guarantee if all the math here is correct as I'm pretty sure we haven't actually used/tested all the functionality we wrote in.

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






学位




Degrees

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​​时,这些功能非常有用。虽然在内部,您的组织可能决定严格遵守弧度,以避免混淆,至少对于这些类,您可以使用最有意义的类型。例如,公开使用的API或GUI API可以使用 Degrees ,而您的大量数学/ trig或内部使用可能使用 Radians 。考虑以下类/打印函数:


Some sample usage

These really benefit when being used an an API. While, internally, your organization might decide to strictly stick with degrees or radians to avoid mixups, at least with these classes you can use the type that makes the most sense. For example, publicly consumed APIs or GUI APIs can use Degrees whereas your heavy math/trig or internal usage might use Radians. Considering the following classes/print function:

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

是的,代码是相当困惑(和可怕的歧义),但是没关系!

Yeah, the code is pretty contrived (and terribly ambiguous) but that's OK! Just goes to show how it can help reduce accidental 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

然后,它们对于基于角度实现其他数学概念非常有用,例如极坐标: / p>

Then they can be really useful for implementing other mathematical concepts based on angles, such as polar coordinates:

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

然后最后, $ c> Point2D 类(或使用System.Windows.Point)隐式转换 Polar :

Then finally, you could implement a Point2D class (or use the System.Windows.Point) with implicit conversions to/from Polar:

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

正如我所说,我想再次通过这些类,并且可能会消除 double 隐式转换为 Radians ,以避免可能的一些情况混淆和编译器模糊。在我们创建静态的 ONE_PI HALF_PI (等等)字段之前, double

As I said, I want to take another pass at these classes, and probably eliminate the double implicit conversions to Radians to avoid a couple corner case mixups and compiler ambiguities that are possible. Those were actually there before we created the static ONE_PI, HALF_PI (and so on) fields and we were converting from some multiple of the Math.PI double.

编辑:这是 Polar 类作为附加隐式转换的演示。它利用了 Radians 类(以及其隐式转换)和辅助方法以及 Point2D 类。我没有包括在这里,但是 Polar 类可以轻松地实现操作与 Point2D 类交互,但是aren

Here's the Polar class as a demonstration of additional implicit conversions. It takes advantage of the Radians class (and thus its implicit conversions) and the helper methods on it and the Point2D class. I haven't included it here, but the Polar class can easily implement operators interacting with the Point2D class but those aren't relevant for this discussion.

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

这篇关于我应该何时在C#中定义(显式或隐式)转换运算符?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆