11

C# のあまり知られていない機能は、暗黙的または明示的なユーザー定義の型変換を作成できることです。私は C# コードを 6 年間書いていますが、一度も使用したことがありません。そのため、良い機会を逃してしまうのではないかと心配しています。

ユーザー定義の変換の正当で有効な使用法とは? カスタム メソッドを定義するよりも優れている例はありますか?

--

Microsoft には、変換に関するいくつかの設計ガイドラインがあり、最も関連性の高いものは次のとおりです。

エンド ユーザーがそのような変換を明確に期待していない場合は、変換演算子を提供しないでください。

しかし、コンバージョンが「期待される」のはいつでしょうか? おもちゃの数のクラス以外では、実際のユースケースを理解できません。


回答で提供されている例の要約を次に示します。

  • ラジアン/度/ダブル
  • 極座標/Point2D
  • ケルビン/華氏/摂氏

パターンは次のように思われます: 暗黙的な変換は、数値/値型を定義するときにほとんど (のみ?) 有用であり、変換は式によって定義されます。振り返ってみると、これは明らかです。それでも、非数値クラスも暗黙の変換の恩恵を受けることができるのだろうか..?

4

6 に答える 6

9

異なる型への、または異なる型からの自然で明確な変換がある場合は、変換演算子を使用できます。

たとえば、温度を表すデータ型があるとします。

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

}

暗黙の演算子を使用すると、double を温度変数に割り当てることができ、ケルビンとして自動的に使用されます。

Temperature a = new Temperature(100, TemperatureScale.Celcius);
Temperature b = 373.15; // Kelvin is default
于 2012-08-26T01:28:24.027 に答える
5

コメントで述べたように、度と回転は、特にAPI間でdouble値を混同しないようにするための良い例です。

現在使用しているクラスRadiansとクラスを取り出しました。Degrees今(久しぶりに)それらを見て、それらをクリーンアップして(特にコメント/ドキュメント)、適切にテストされていることを確認したいと思います。ありがたいことに、私はそうするためにスケジュールに時間をかけることができました。いずれにせよ、これらは自己責任で使用してください。ここでのすべての計算が正しいかどうかは保証できません。私たちが書いたすべての機能を実際に使用/テストしたわけではないからです。

ラジアン

/// <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を使用するときに本当に役立ちます。内部的には、組織は混乱を避けるために度またはラジアンに厳密に固執することを決定する場合がありますが、少なくともこれらのクラスでは、最も意味のあるタイプを使用できます。たとえば、公的に消費されている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));
}

ええ、コードはかなり工夫されています(そしてひどく曖昧です)が、それは大丈夫です!偶発的な取り違えを減らすのにどのように役立つかを示します。

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を作成する前に実際に存在し、 doubleの倍数から変換していました。ONE_PIHALF_PIMath.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);
    }
}
于 2012-09-01T22:40:49.690 に答える
1

ショップ アプリケーションに使用している製品 (おもちゃなど) のクラスがあるとします。

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


エンド ユーザーがそのような変換を明確に期待していない場合は、変換演算子を提供しないでください。

Microsoft がこれを意味するのは、変換演算子を提供しても、予期しない結果が返されないということですクラスの最後の例を使用するとProduct、これは予期しない結果を返すものです。

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

明らかに誰も実際にこれを行うことはありませんが、他の誰かがこのProductクラスを使用して明示的な文字列変換を使用したとしても、100 倍の価格が返されるとは予想していなかったでしょう。

お役に立てれば!

于 2012-08-26T01:16:43.127 に答える
0

一般に、2 つのものが論理的に変換可能である場合。このような状況でそれらを使用して、より流暢なコードを提供します。また、期待どおりに機能しない言語機能を回避するために、それらを使用することもあります。

これは、私が本番環境で使用したものに似た最後のアイデアを示す、非常に単純で不自然な例です...

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;
    }
}
于 2012-08-26T01:20:01.020 に答える