【问题标题】:When should I define a (explicit or implicit) conversion operator in C#? [closed]何时应该在 C# 中定义(显式或隐式)转换运算符? [关闭]
【发布时间】:2012-08-21 01:13:54
【问题描述】:

C# 的一个鲜为人知的特性是可以创建隐式或显式user-defined type conversions。 我已经编写 C# 代码 6 年了,但我从未使用过它。所以,恐怕我会错失良机。

用户定义的转换有哪些合法、良好的用途?您是否有比仅定义自定义方法更好的示例?

--

事实证明,微软有一些关于转化的design guidelines,其中最相关的是:

如果转换不明确,不要提供转换运算符 最终用户的期望。

但“预期”何时会发生转化?除了玩具编号类之外,我想不出任何真实世界的用例。


以下是答案中提供的示例的摘要:

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

模式似乎是:隐式转换在定义数值/值类型时主要(仅?)有用,转换由公式定义。回想起来,这是显而易见的。不过,我想知道非数字类是否也可以从隐式转换中受益..?

【问题讨论】:

  • 一个实际应用的例子:在我的工作中,我们创建了不同的DegreesRadians 类来管理角度。它们具有相互和Double 之间的隐式转换,并且极大地简化了我们对角度的使用,并且几乎消除了在各种三角函数中意外使用度数而不是弧度(反之亦然)的情况.
  • 非常好!就像您正在使用 C# 的用户定义转换重新创建 F# 的度量单位。
  • 我避免使用转换运算符,因为它们无法像方法和构造函数那样通过 IntelliSense 发现,这样更容易错过定义的转换。

标签: c# casting operator-overloading implicit-conversion explicit-conversion


【解决方案1】:

没有普遍的答案。我会谨慎使用它,并且只有在保持代码易于理解和直截了当(即它显示出预期的行为)的情况下。

所以我可以根据一个实际的例子给你一个答案,如果你跟着它,你就会明白什么时候使用转换运算符,什么时候最好不要使用:

我最近想用一种更简单的方法来处理 Guid。我的设计目标是:简化语法和初始化,简化转换和变量分配。

如您所知,如果您需要创建 GUID,使用起来有点麻烦:

示例 1:

开箱即用:

var guids = new Guid[] {
  new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f1"), new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f2"),
  new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f3")
};

如果您可以将 GUID 字符串隐式转换为字符串,例如:

var guids = new EasyGuid[] {
    "2f78c861-e0c3-4d83-a2d2-cac269fb87f1", "2f78c861-e0c3-4d83-a2d2-cac269fb87f2",
    "2f78c861-e0c3-4d83-a2d2-cac269fb87f3"
};

这将允许将 GUID 列表直接从 JSON 文件粘贴到 C# 代码中。

示例 2:

要初始化一个数组,您需要执行以下操作:

var guids = new Guid[30];
for (int i = 0; i < 30; i++)
{
    guids[i] = System.Guid.Empty; // Guid with 000...
}

像这样的指导不是更容易吗:

var guids = new EasyGuid[30]; // create array with 30 Guids (value null)

两个示例中的 guid 可以像这样使用

foreach (Guid g in guids)
{
        g.Dump();
}

换句话说,它们可以在需要使用时隐式转换为“普通”Guid。在第二个示例中,如果它们为 null,则会在运行中隐式分配 Empty Guid。

你怎么能做到这一点?您不能从 System.Guid 继承。但是您可以使用隐式转换。看看这个类,我称之为EasyGuid,它使上面的声明成为可能:

/// <summary>
/// Easy GUID creation
/// Written by Matt, 2020
/// </summary>
public class EasyGuid
{
    // in case you want to replace GUID generation 
    // by RT.Comb, call Provider.PostgreSql.Create()
    private static System.Guid NewGuid => System.Guid.NewGuid();

    private System.Guid _guid = EasyGuid.NewGuid;

    public EasyGuid()
    {
        _guid = NewGuid;
    }

    public EasyGuid(string s)
    {
        _guid = new System.Guid(s); // convert string to Guid
    }

    // converts string to Guid
    public static implicit operator EasyGuid(string s) => new EasyGuid(s);

    // converts EasyGuid to Guid, create empty guid (Guid with 0) if null
    public static implicit operator System.Guid(EasyGuid g)
                                    => (g == null) ? System.Guid.Empty : g.ToGuid();

    // converts EasyGuid to Guid?, null will be passed through
    public static implicit operator System.Guid?(EasyGuid g)
                                    => (g == null) ? null : (Guid?)g.ToGuid();
                                    
    public override string ToString() => _guid.ToString();
    public System.Guid ToGuid() => _guid;
}

您可以看到EasyGuid 可以将字符串隐式转换为 EasyGuid,并且可以通过调用 ToGuid() 隐式或显式将 EasyGuid 转换为 Guid。它也可以打印为字符串,因为我已经覆盖了.ToString()

最后,我希望能够轻松地动态生成新的 GUID。我通过写作实现了这一点。

    // converts EasyGuid to Guid, create empty guid (Guid with 0) if null
    public static implicit operator System.Guid(EasyGuid g)
                                    => (g == null) ? EasyGuid.NewGuid : g.ToGuid();
                                    

会有这样的效果

var guids = new EasyGuid[30]; 

将在转换为 GUID 后立即生成新的 GUID。但是我从@OskarBerggren 得到了反馈,这种方法虽然很容易实现,但会引起混淆——对于其他阅读它的人来说,代码将不再明显(谢谢你,Oskar 的这个提示!)。它还可能导致意外问题(错误)。记住微软的话:

如果最终用户没有明确期望这种转​​换,则不要提供转换运算符。

所以我不是通过隐式转换来实现的,而是使用如下扩展方法:

public static class Extensions
{
    public static System.Guid[] ToGuids(this EasyGuid[] guidArray, bool replaceNullByNewGuid = false)
        => guidArray.ToList().ToGuids(replaceNullByNewGuid).ToArray();
        
    public static List<System.Guid> ToGuids(this List<EasyGuid> easyGuidList, bool replaceNullByNewGuid = false)
    {
        var guidList = new List<Guid>();
        foreach (var g in easyGuidList)
        {
            Guid result = (g!=null) ? g : ((replaceNullByNewGuid) ? new EasyGuid().ToGuid() : System.Guid.Empty);
            guidList.Add(result);
        }
        return guidList;
    }
}

这更简单,因为现在您可以选择:

// shorter: .ToGuids(true)
var guids = new EasyGuid[30].ToGuids(replaceNullByNewGuid: true);

如果你只是想创建一个带有空 Guid 的数组:

var guids = new EasyGuid[30].ToGuids();

可以(GUIDS 列表相同)。

此示例表明隐式转换运算符很容易因意外行为而引起混淆。有时,最好使用扩展方法(如图所示)。

我认为这个例子表明,在某些情况下转换运算符可以让您的生活更轻松,而在其他情况下,您应该停下来思考并找到更明显的实施方式。


为了完整性:其他情况是:

var eg1 = new EasyGuid();  // simple case: new Guid
Guid g = eg1; g.Dump();    // straight-forward conversion
    
EasyGuid eg2 = null;       // null-handling
Guid g2 = eg2; g2.Dump();  // converted to 00000000-0000-0000-0000-000000000000
Guid? g3 = eg2; g3.Dump(); // will be null

【讨论】:

  • 使用隐式转换为包装类型的包装类型更改类型的默认构造函数的行为...是的,它有效。但我不认为隐藏转换是一个好主意,它会增加复杂性......除非你以某种方式使用 C# 作为 DSL,也许。
  • @Eldritch:正如我所写,我会小心使用它。真正需要转换运算符的用例并不多。在这里,它也不是真正需要的,它只是简化了语法。用不用,看个人喜好了。有时,最好有一个例子,知道它的样子——它可以让你估计你是否想走那条路。
  • 特别是对于数组初始化,C# 中的期望是分配某个大小的数组将使数组的所有元素都具有该类型的默认值——通常是0。拥有一个为每个元素生成随机值的类型感觉就像一个陷阱。
  • @OskarBerggren - 同意,我已经更改了代码,现在它在数组示例中分配了一个空 guid。
【解决方案2】:

我用它来实现从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");
    }
}

【讨论】:

  • 有趣!这使您可以只定义一次YearsFrom,并为其提供几种可能的返回类型。通常我会为每个所需的返回类型编写一个简短的重载,但您的方法可以更好地扩展要定义的函数数量。它也不会丢失任何编译时类型错误检查。
【解决方案3】:

如 cmets 中所述,度数和旋转是避免混淆双精度值的好例子,尤其是在 API 之间。

我取出了我们当前正在使用的 RadiansDegrees 类,它们就在这里。现在看看它们(经过这么长时间)我想清理它们(尤其是 cmets/文档)并确保它们经过适当的测试。谢天谢地,我设法在日程安排中抽出时间这样做。无论如何,使用这些需要您自担风险,我不能保证这里的数学是否正确,因为我很确定我们实际上没有使用/测试过 我们编写的所有功能。

弧度

/// <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/从 Polar

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

正如我所说,我想再通过这些课程,并可能消除 doubleRadians 的隐式转换,以避免可能出现的一些极端情况混淆和编译器歧义。在我们创建静态 ONE_PIHALF_PI(等等)字段之前,它们实际上就在那里,我们正在从 Math.PI double 的一些倍数进行转换。

编辑:这是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);
    }
}

【讨论】:

  • 我认为您的 Reduce() 在负角上无法按预期工作。
  • Radians/Degrees/double 和 Polar/Point2D 都是使用隐式转换使代码更安全的好例子,所以我将其标记为答案。我想知道非数字类是否也可以从隐式转换中受益。
  • @EldritchConundrum 我不会感到惊讶。这是一种从我们拥有的旧的、旧的 Flash 系统移植而来的旧方法。它旨在将任何角度转换为 0 到 2pi 之间的等效角度(这意味着将负角度转换为等效正角度)。我的测试确实表明它适用于负角度,你能提供你使用的角度还是你得到的行为(这是预期的)?无论如何,这是我想重写的方法的一个例子,以便更清楚。
  • @EldritchConundrum 一些非数字的快速示例可能是本地化字符串“en-us”到自定义对象,或者我们过去添加了将Color 转换为等效的SolidColorBrush (为了方便)。它绝对是 Microsoft 添加的工具之一,虽然不是严格必要的,但旨在使代码更具可读性和可维护性(如果没有被滥用!)。刚刚记得,我回答了一个问题,使用隐式运算符表示编译时检查的“兼容类型”:stackoverflow.com/questions/10622694/handling-unsupported-types/…
  • 您在列表初始化语法中使用隐式转换是一种有趣的滥用,即“将 C# 用作 DSL”类型。我更不确定从 Color 到 SolidColorBrush 的转换,它们可能最好保持明确。而且我认为您的 Reduce() 值小于 -2*pi 仍返回负数,但我不介意,因为我不打算使用它:)
【解决方案4】:

当与不同类型进行自然而清晰的转换时,您可以使用转换运算符。

例如,假设您有一个表示温度的数据类型:

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

【讨论】:

  • 一般来说,任何度量单位都适合。
  • 您不应该将其实现为 3 个单独的类(开尔文、华氏温度、摄氏温度),并在它们自身和双精度之间进行隐式转换吗?
  • @EldritchConundrum:这取决于您决定对象描述的内容。我会说它本质上描述了一个能量水平,而开尔文/华氏/摄氏度只是描述它的不同方式。比较DateTime 是如何描述一个时间点的,Local/Universal 是描述它的不同方式。
【解决方案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;
    }
}

【讨论】:

  • 是的,它看起来有点做作......而这里,隐式转换也可以写成“int n = code1 + code2;”这没有任何意义。我认为只写“switch (code1.Id)”就可以了。
  • 你是对的。但有时这是你必须抓住的机会。无论你做什么,你都无法战胜愚蠢。
【解决方案6】:

假设您有一个用于商店应用程序的产品(例如玩具)类:

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。

希望这会有所帮助!

【讨论】:

  • 已经有一个 ToString() 方法。转换如何更好?这似乎不是一个引人注目的用例。我认为 MS 意味着您应该避免为此类定义转换。
  • 我不是(string) myProduct 示例的忠实拥护者,因为人们希望它由ToString 处理。一个更好的例子是,如果您将字符串转换为 Products。
  • 我只是以字符串转换为例;您可以使用您认为您的类可以转换为的任何转换类型。例如,对于 Product 类,您可以定义一个返回价格的 explicit int operator
猜你喜欢
  • 2012-03-22
  • 1970-01-01
  • 1970-01-01
  • 2010-10-27
  • 1970-01-01
  • 2016-03-29
  • 1970-01-01
  • 1970-01-01
  • 2013-08-25
相关资源
最近更新 更多