生成许多​​具有与float(System.Single)相同功能的结构类型

时间:2018-11-28 19:05:56

标签: c# code-generation

我已经创建了一个代码库,其中我非常依赖float结构。但是浮动可能意味着任何事情。秒,米,弧度,公斤,秒/秒,弧度/秒等。正确的命名很有帮助,但仍然很容易混淆。因此,我开始为每个单元编写包装浮点数的结构。但是编写这样的结构变得越来越麻烦:

  • 存储其基础浮动值
  • 所有其运算符(<,<=,>,> =,==,!=,-,+,*,/)
  • 适当的Equals,GetHashCode和ToString方法
  • IEquatable接口的实现

最简单的生成这种结构的方法是什么,这样我就不必维护太多代码了?

奖金问题:通过这种方式,我仍然能够添加一些特殊的运算符。例如,我想添加一个在Meters中变成Seconds / MetersPerSecond的运算符。

编辑:我在.Net Framework 4.7和C#语言级别7.3

这是我的示例Seconds结构。如您所见,Meters结构看起来完全一样。

public struct Seconds : IEquatable<Seconds>
{
    public Seconds(float value)
    {
        this.Value = value;
    }

    public float Value { get; }

    public static implicit operator Seconds(float value) => new Seconds(value);

    public static implicit operator Seconds(TimeSpan value) => new Seconds((float)value.TotalSeconds);

    public static Seconds operator +(Seconds a, Seconds b) => new Seconds(a.Value + b.Value);

    public static Seconds operator -(Seconds a, Seconds b) => new Seconds(a.Value - b.Value);

    public static bool operator >(Seconds a, Seconds b) => a.Value > b.Value;

    public static bool operator <(Seconds a, Seconds b) => a.Value < b.Value;

    public static bool operator >=(Seconds a, Seconds b) => a.Value >= b.Value;

    public static bool operator <=(Seconds a, Seconds b) => a.Value <= b.Value;

    public static bool operator ==(Seconds a, Seconds b) => a.Equals(b);

    public static bool operator !=(Seconds a, Seconds b) => !a.Equals(b);

    public override int GetHashCode() => this.Value.GetHashCode();

    public override bool Equals(object obj)
    {
        if (obj is Seconds)
        {
            return this.Equals((Seconds)obj);
        }

        return false;
    }

    public bool Equals(Seconds other) => other.Value == this.Value;

    public override string ToString() => $"{this.Value.ToString("F2", CultureInfo.InvariantCulture)}s";
}   

2 个答案:

答案 0 :(得分:1)

尝试使用F#中的Units of Measure功能。它在编译时强制执行度量单位,不影响性能。如果公共表面保持简单,则可以很容易地从C#中使用F#进行组装。拍到浮动对象上的计量单位将编译为IL中的System.Double。

就奖金而言,计量单位由编译器维护和验证。如果将米除以秒,结果将以每秒米为单位。

答案 1 :(得分:1)

如果您想使用C#,可以使用一个简单的Yellicode模板来生成样板代码(使用一些TypeScript)。这是一个生成部分结构(struct-types.template.ts)的模板。

import { Generator, TextWriter } from '@yellicode/templating';
import { CSharpWriter } from '@yellicode/csharp';

var structNames = ['Seconds', 'Meters']; // add more names here 

Generator.generate({ outputFile: './structs.cs' }, (output: TextWriter) => {
    var csharp = new CSharpWriter(output);
    structNames.forEach(structName => {     
        const struct: StructDefinition = { name: structName, accessModifier: 'public', isPartial: true, implements: [`IEquatable<${structName}>`] } as StructDefinition;
        csharp.writeStructBlock(struct, () => {
            // Ctor            
            csharp.writeMethodBlock({ isConstructor: true, name: structName, accessModifier: 'public', parameters: [{ typeName: 'float', name: 'value' }] }, () => {
                csharp.writeLine('this.Value = value;');
            });
            csharp.writeLine();
            // Value            
            csharp.writeAutoProperty({ typeName: 'float', name: 'value', accessModifier: 'public', hasGetter: true });
            csharp.writeLine();
            // Operators 
            csharp.writeLine(`public static implicit operator ${structName}(float value) => new ${structName}(value);`);
            csharp.writeLine();
            // ... other operators omitted for brevity, type-specific conversion operators would be in a partial file
            // Methods
            csharp.writeLine('public override int GetHashCode() => this.Value.GetHashCode();');
            csharp.writeLine();
            // ... other methods omitted for brevity
        })
        csharp.writeLine();
    })
}); 

配置文件(codegenconfig.json)应该如下所示:

{
    "compileTypeScript": true,
    "templates": [
        {
           "templateFile": "struct-types.template.ts"
        }
    ]
}

快速入门(确保已安装NPM,然后打开命令提示符):

  1. npm install @yellicode/cli -g
  2. 使用上述文件创建目录并运行npm init -y
  3. npm install --save-dev @yellicode/templating @yellicode/csharp
  4. 运行yellicode --watch生成代码。

如果要为每个结构使用单独的输出文件,请从structNames.forEach(..循环开始,并为每个结构调用Generator.generate(...