为什么C#中的“using别名”不是默认使用的?

7
请看下面的代码。
using System.ComponentModel.DataAnnotations;

namespace Foo
{
    public class Bar
    {
        [Required, MaxLength(250)]
        public virtual string Name { get; set; }
    }
}

如果你没有一个高级的集成开发环境(背后进行各种查找和静态分析),"Required"和"MaxLength"来自哪里就很模糊了。特别是当导入了几个名称相似的命名空间时。

作为相对新手的C#程序员,我总是难以确定某些东西的来源。特别是在像StackOverflow这样的地方查看其他代码片段时。

using DataAnnotations = System.ComponentModel.DataAnnotations;

namespace Foo
{
    public class Bar
    {
        [DataAnnotations.Required, DataAnnotations.MaxLength(250)]
        public virtual string Name { get; set; }
    }
}

现在“必填”和“最大长度”很明显了。您可以再进一步做一些类似以下的事情:
using Required = System.ComponentModel.DataAnnotations.RequiredAttribute;
using MaxLength = System.ComponentModel.DataAnnotations.MaxLengthAttribute;

namespace Foo
{
    public class Bar
    {
        [Required, MaxLength(250)]
        public virtual string Name { get; set; }
    }
}

这现在与PHP和JS ES6的工作方式非常相似。 我很好奇为什么这不是C#的默认设置?为什么几乎每个我交谈过的C#开发人员都认为别名是一种不好的做法?也许有一些潜在的性能原因吗?

我认为你在问题中已经有了答案:“除非你有一个花哨的IDE”。我想绝大多数C#开发人员都在使用Visual Studio。我怀疑性能是否会产生任何影响,这应该是编译时间的事情。 - Evan Trimboli
1
@OP,您所建议的做法会导致每个 .cs 文件中使用的每种类型都需要一个 using 语句。如果您从 System.ComponentModel.DataAnnotations 命名空间中使用了 10 种类型,您是想要 10 个 using 语句还是一个? - JLRishe
1
在这种情况下,我仍然只有一个using语句。using DataAnnotations = System.ComponentModel.DataAnnotations; 这是关于将实际使用的内容与其using语句绑定在一起的问题。 - Brad
@BradJones 那么每次访问这些类型时,您必须键入 DataAnnotations.[TypeName] 而不是仅使用 [TypeName]。这真的有益吗?我认为这只会使代码变得冗长无比。 - JLRishe
你提到“默认值”的概念是在什么上下文中?你是编写代码的人,可以将其“默认”为任何你编写的内容。当然,那些你刚才忽略的“花哨”编辑器可能会自动插入类似的内容,但你所要表达的重点是,如果你不使用那个花哨的编辑器,那该怎么办...所以你的问题实际上没有多少意义,并且自相矛盾。 - Erik Funkenbusch
显示剩余3条评论
1个回答

7

为什么类型/定义的来源很重要?

如果你真的关心一个类型所在的命名空间,Visual Studio有多种方法可以查找,我最喜欢的两种方法如下:

  • 悬停在类型/声明上方。 这通常会显示完整的类型名称。(悬停在new SomeType()语句上会显示方法名,这是应用于属性的内容。)
  • 按F12 / 转到定义。 即使您没有定义的源代码,使用 F12右键单击 -> 转到定义 也会带您进入元数据文件,显示类型的所有公共成员。这不适用于关键字(out, ref, return, null等),但适用于基本别名类型(int, string等)和传统类型(enum, interface, class, struct等)。这包括命名空间、类型名称和所有公共API成员。如果有XML文档,它们也会被包含在内。如果你F12一个扩展方法,它会带你进入该扩展方法的类元数据。如果您认为某个方法是由某些东西注入的,这非常有助于确定它来自哪里
现在并不是真正难以确定类型来自哪个命名空间了。那么使用别名“using”何时才真正需要呢?
实际场景:我一直在为XNA框架的Windows Forms模型工作。XNA框架有一个“Color”类型,而我的框架也有一个“Color”类型。现在我经常同时使用这两个命名空间,但只需要其中一个“Color”类型被本地使用。通常情况下,我的“using”语句列表包括以下内容:
using XnaColor = Microsoft.Xna.Framework.Color;
using Color = Evbpc.Framework.Drawing.Color;

这样可以解决一个模棱两可的问题。

为什么使用别名不是默认设置?

可能是因为它们几乎从不必要。我们并不真正需要它们。如果你担心类型来自哪个命名空间,那么进行快速查找比别名更容易,并且强制定义命名空间。在那种情况下,您可能会完全禁用using语句并完全限定所有内容。

我曾经使用using别名的最大两个用例是:

  1. Resolve ambiguity between types. See example above.
  2. Resolve ambiguity between namespaces. Same idea as above, but I alias a whole namespace if a lot of types are duplicated.

    using XnaF = Microsoft.Xna.Framework;
    using Evbpc.Framework.Drawing;
    
如果您使用Visual Studio生成代码、导入类型等,则不会使用别名。相反,Visual Studio将根据需要完全限定名称。右键单击波浪线并得到A.B.Type作为唯一选项,而不是using A.B?那么通常这是一个很好的别名使用点。
但是我要警告您,使用别名似乎会增加可维护性要求。(可能没有支持的数字,但我不会撒谎——在我的几个别名项目中,我经常忘记如何/什么命名别名。)
通常,在我的经验中,如果您必须使用using别名,则可能违反了某个规则。
为什么我们甚至不经常使用它们呢?
因为它们很糟糕。它们使代码更难阅读(以您的DataAnnotations.MaxLength示例为例,我为什么需要阅读它?我不在乎MaxLengthSystem.ComponentModel.DataAnnotations中,我只关心它是否设置正确),它们会使代码混乱(现在我被迫记住一个属性在System.ComponentModel.DataAnnotations而不是System.ComponentModel.DataAnnotations.Schema中),而且它们通常很笨重。
以您之前的示例为例,我有一个Entity Framework项目,在类上有以下属性:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Key, Column(Order = 2)]
[MaxLength(128)]
public string UserId { get; set; }

[ForeignKey(nameof(UserId))]
public virtual ApplicationUser User { get; set; }

现在有了你的示例,我会选择以下之一:
using DataAnnotations = System.ComponentModel.DataAnnotations;

[DataAnnotations.Key, DataAnnotations.Schema.Column(Order = 2)]
[DataAnnotations.MaxLength(128)]
public string UserId { get; set; }

[DataAnnotations.Schema.ForeignKey(nameof(UserId))]
public virtual ApplicationUser User { get; set; }

或者:

using DataAnnotations = System.ComponentModel.DataAnnotations;
using Schema = System.ComponentModel.DataAnnotations.Schema;

[DataAnnotations.Key, Schema.Column(Order = 2)]
[DataAnnotations.MaxLength(128)]
public string UserId { get; set; }

[Schema.ForeignKey(nameof(UserId))]
public virtual ApplicationUser User { get; set; }

或者更糟糕的是:

using KeyAttribute = System.ComponentModel.DataAnnotations.KeyAttribute;
using MaxLengthAttribute = System.ComponentModel.DataAnnotations.MaxLengthAttribute;
using ColumnAttribute = System.ComponentModel.DataAnnotations.Schema.ColumnAttribute;
using ForeignKeyAttribute = System.ComponentModel.DataAnnotations.Schema.ForeignKeyAttribute;

[Key, Column(Order = 2)]
[MaxLength(128)]
public string UserId { get; set; }

[ForeignKey(nameof(UserId))]
public virtual ApplicationUser User { get; set; }

对不起,但那些只是糟糕的。这就是为什么你跟每个开发者谈论时他们都会避免使用这些并认为这是一个坏主意的原因。然后我将使用别名,而 聪明地[1] 导入命名空间并处理可能出现类型冲突的非常微小的问题。

如果你真的找不到某个类型所在的命名空间(例如你从 Stack Overflow 上获取代码),那么访问MSDN,进入Library并搜索该类型。(即搜索KeyAttributeMaxLengthAttribute,第一个链接就是 API 参考文档。)

[1]: 通过“聪明”我指的是负责任、小心地这样做。不要盲目地导入/使用命名空间,尽可能地限制它们。SRP 和多态通常使我们能够在每个文件中保持 using 列表相当小。


谢谢@EBrown,我觉得这只是需要适应的问题。刚刚发现https://learn.microsoft.com/en-us/dotnet/api/非常有帮助。 - Brad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接