c# 中 typedef 或子类化字符串的替代方法

本文关键字:方法 字符串 typedef 子类 | 更新日期: 2023-09-27 18:32:33

情况

我的类在内部处理许多不同类型的文件路径:一些本地的,一些远程的;一些相对的,一些绝对的。

过去,它的许多方法将它们作为string相互传递,但是很难准确跟踪每个方法所期望的路径类型。

所需的修复

所以我们基本上想typedef四种不同的类型来stringRemoteRelativeLocalRelativeRemoteAbsoluteLocalAbsolute。通过这种方式,静态类型检查器可以帮助开发人员确保他们提供并期望具有正确语义的string

遗憾的是,string sealed在 BCL 中,因此我们无法通过简单的继承来做到这一点。而且没有简单的typedef,所以我们也不能那样做。

实际修复

我最终创建了四个不同的简单类,每个类都包含一个readonly string

public struct LocalAbsolutePath {
    public readonly string path;
    public LocalAbsolutePath(string path) {
        this.path = path;
    }
}

这基本上有效,但它最终增加了一些不必要的冗长。

问题:我是否忽略了任何自然适合简单 C# 语法的替代方法?

就像我上面提到的,C 风格的typedef string LocalAbsolutePath;甚至 F# 风格的type LocalAbsolutePath = string将是我的梦想。但是,即使是自定义类方向的一步也会很棒。

c# 中 typedef 或子类化字符串的替代方法

你的解决方案很好。您可以通过向string添加类型转换来对抗额外的冗长,让您在string的任何地方使用LocalAbsolutePath

public struct LocalAbsolutePath { // Making it a class would be OK too
    private readonly string path; // <<=== It is now private
    public LocalAbsolutePath(string path) {
        this.path = path;
    }
    public static implicit operator string(LocalAbsolutePath p) {
        return p.path;
    }
}

您应该做的是保留当前为路径类型创建四个不同类的方法(甚至让它们继承相同的基类(,以便您可以将方法限制为仅接收这四个Path对象中的一个。

虽然我不觉得var myPath = new LocalAbsolutePath("path")真的比var myPath = "path"更冗长,因为它缺乏简洁性,它在显式性上弥补了,但如果你真的想要,你可以在你的类和字符串之间实现隐式强制转换运算符,并有这项工作:

 public static implicit operator LocalAbsolutePath(string path)
 {
     return new LocalAbsolutePath(path);
 }

现在你可以做:

LocalAbsolutePath myPath = "Path String";

我创建了一个名为 LikeType 的 NuGet 包,它在 C# 类中提供了类似typedef的行为。

这是您将如何使用它:

class CustomerId : LikeType<string>
{
    public CustomerId(string id) : base(id) { }
}

以下是该类型的行为方式:

void ShowTypeBehavior()
{
    var customerId = new CustomerId("cust-001"); // create instance with given backing value
    string custIdValue = customerId; // implicit cast from class to backing type, sets 'custIdValue' to "cust-001"
    var otherCustomerId = new CustomerId("cust-002");
    var areEqual = customerId == otherCustomerId; // false
    var areNotEqual = customerId != otherCustomerId; // true
    var areEqualUsingMethod = customerId.Equals(otherCustomerId); // false
    var customerIdCopy = new CustomerId("cust-001"); // create separate instance with same backing value
    var isCopyEqual = customerId == customerIdCopy; // true. Instances are considered equal if their backing values are equal.
}

由于我在我正在从事的项目中设定了相同的目标,并且我从这里的答案中受益匪浅,我想我会分享我最终得到的解决方案。 处理 null,尤其是在单元测试断言中,几乎让我发疯。 以下当然会失败:

string someStringVar = null;
MyStringType myStringType = new MyStringType(someStringVar);
MyStringType myStringTypeNull = null;
Assert.AreEqual(myStringType, myStringTypeNull);

使用静态 Parse(( 代替公共构造函数更令人满意,因为它让我返回 null。 这通过:

string someStringVar = null;
MyStringType myStringType = MyStringType.Parse(someStringVar);
MyStringType myStringTypeNull = null;
Assert.AreEqual(myStringType, myStringTypeNull);

另外,我不希望从字符串隐式转换为MyStringType - 在我看来,这首先消除了这样做的一些有意识的编码者的好处。 允许从字符串隐式转换意味着调用具有 MyStringType 参数的方法将接受字符串,我不希望这样做,因为具有大量字符串参数的方法很容易出错。 隐含地进行的反向转换对我来说更有意义。

最后,我认为这是易于重用的泛型的理想案例。

无论如何,这是我最终得到的:

public class StringType<T> where T:class
{
    private readonly string _str;
    protected StringType(string str)
    {
        _str = str;
    }
    public static implicit operator string(StringType<T> obj)
    {
        return obj == null ? null : obj._str;
    }
    public override string ToString()
    {
        return _str;
    }
    public override int GetHashCode()
    {
        return _str.GetHashCode();
    }
}

public class MyStringType : StringType<MyStringType>
{
    protected MyStringType(string str) : base(str) { }
    public static MyStringType Parse(object obj)
    {
        var str = obj is string ? (string)obj : (obj == null ? null : obj.ToString());
        return str == null ? null : new MyStringType(str);
    }
}

当然欢迎评论/改进/简化!

也许我在这里吠错了树,但据我所知,typedef在 C# 中作为类型别名实现的。设置类型别名非常简单:

using LocalAbsolutePath = System.String;

然后,您可以开始使用LocalAbsolutePath作为有效类型。或多或少像这样:

LocalAbsolutePath thisPath = "c:''thisPath";

根据您的帖子,我感觉到这就是您要找的。希望我是对的...!