You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#如何为类的{get;set;}属性实现隐式赋值转换

问题说明

我希望创建一种表现与double值一致、同时可附加额外功能的类类型。我了解隐式转换实现方案,但该方案无法为已存在的类实例直接赋值:

public interface IUnit
{
    double ToScaled(double v);
    double ToSI(double v);
}

public class ScalableNumber
{
    public IUnit Unit { get; set; }
    double val;

    public double SI { get => val; set => val = value; }
    public double Scaled { get => Unit?.ToScaled(val)??val; set => val = (Unit == null)? value : Unit.ToSI(value); }
    
    public ScalableNumber(IUnit u)
    {
        Unit = u;
    }
    // 隐式转double,实现类似get的隐式行为
    public static implicit operator double(ScalableNumber x) => x.val;

    // 想要实现隐式set,直接把double赋值给val
    // 从double隐式构造ScalableNumber的方案不可行,会丢失原有单位
    public static implicit operator ScalableNumber(double x) => new ScalableNumber(null) { val=x};

    // 索引器方案不可行,隐式转换得到的是单值
    public double this[int i] { get => val; set => val = value; }
}
public class Unit:IUnit
{
    public double scale = 1;
    public double ToScaled(double v) => v/scale;
    public double ToSI(double v) => scale * v;
}
public class MyExample
{
    IUnit kilometer, hour, angstroemPerWeek;
    public ScalableNumber way;
    public ScalableNumber time;
    public ScalableNumber speed;

    public MyExample()
    {
        kilometer = new Unit() { scale = 1000 };
        hour = new Unit() { scale = 3600 };
        angstroemPerWeek = new Unit() { scale = 1e-10 / (60 * 60 * 24 * 7) };
        way = new ScalableNumber(kilometer);
        time = new ScalableNumber(hour);
        speed = new ScalableNumber(angstroemPerWeek);
    }
    public static void ExecuteMyExample()
    {
        var myExample = new MyExample();
        myExample.way.Scaled = 100;
        myExample.time.Scaled = 1;
        Console.WriteLine(myExample.Speed);
    }

    public void Calculate()
    {
        // 期望得到单位为埃/周的速度
        // 当前实现得到的是米每秒(无缩放)的速度值
        speed = way / time;
    }
    public string Speed { get { if (time <= 0) return string.Empty;  Calculate(); return speed.Scaled.ToString(); } }
}

当然,我可以在Calculate()方法中编写如下代码完成赋值:

speed.SI = way / time;

但如果可以省略.SI直接完成赋值操作,我就能够复用大量目前针对double类型属性编写的方法。请问C#中是否存在可行方案,无需指定属性名即可将赋值操作直接定向到类的对应内部属性?


回答

首先明确C#的核心语法规则:C#不支持自定义重载赋值运算符=。
对于引用类型(你当前定义的ScalableNumber是class),赋值操作的行为永远是修改变量的引用指向,让变量指向等号右侧的新实例,不可能修改原有实例的内部字段/属性。你现在写的speed = way / time实际执行逻辑是:

  1. 因为没有自定义ScalableNumber之间的除法运算符,编译器自动调用两个ScalableNumber到double的隐式转换,做完除法得到double类型的米/秒速度值
  2. 调用你写的double到ScalableNumber的隐式转换,生成一个Unit为null的全新ScalableNumber实例
  3. 把speed变量的引用,从一开始构造的、绑定了埃/周单位的实例,替换成这个新生成的无单位实例,原有实例直接被丢弃
    这就是你单位丢失、结果不符合预期的根本原因,这个行为是语法层面固定的,没有任何特性可以绕开。

如果要实现你要的、和double行为一致又带单位的效果,有两个可行的落地方案,不需要每次写.SI:

方案1:将ScalableNumber改为值类型(struct)+ 重载所有算术运算符

值类型的赋值是值拷贝,不存在引用替换的问题。你需要做两个改动:

  • 把ScalableNumber从class改为readonly struct,保留你现有的隐式转double逻辑
  • 重载所有你需要用到的算术运算符(加减乘除、比较等),让ScalableNumber之间的运算直接返回携带正确单位的ScalableNumber结果,不要让运算降级到double类型。比如长度除以时间的运算符重载里,直接返回绑定了对应速度单位的结果实例,赋值时就不会丢失单位。
    这个方案的好处是类型行为和double完全一致,所有原来接收double的方法都可以直接传ScalableNumber,不需要改原有代码逻辑。唯一需要注意的是值类型的拷贝开销,对于包装double的小结构体来说这个开销可以忽略。

方案2:保留class引用类型,通过属性封装拦截赋值

如果你一定要用引用类型(比如要支持继承、要做引用传递),可以不要把ScalableNumber类型的字段直接暴露为public字段,而是封装成属性,在属性的set块里自动把传入的double/ScalableNumber值写到原有实例的内部字段,而不是替换引用:

private ScalableNumber _speed = new ScalableNumber(angstroemPerWeek);
public ScalableNumber speed
{
    get => _speed;
    set
    {
        // 赋值时不替换引用,直接把值写到原有实例的SI属性
        _speed.SI = value;
    }
}

这个方案的问题是,所有用到ScalableNumber的地方都要写这种封装属性,没法直接用public字段,而且如果等号右侧是新的带其他单位的ScalableNumber,你还要自己处理单位转换逻辑。

不存在任何"黑科技"语法可以让你在直接给class类型的public字段赋值double时,自动修改原有实例内容,所有声称能实现这个效果的方案本质都是上面两种思路的变种。如果你不想改动类型定义、也不想写属性封装,那老老实实写.SI =是成本最低、可读性最好的选择。


内容的提问来源于stack exchange,提问作者SDG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 16:28:03