65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 2.0 别名

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.35/5 (23投票s)

2005年10月3日

6分钟阅读

viewsIcon

76331

对 C# 2.0 别名的通用描述。

引言

毫无疑问,在讨论别名之前,需要快速回顾一下它们的使用范围。当然,要处理这个主题,对命名空间进行简要讲解是必要的,所以我们开始吧。C# 命名空间允许您创建一个系统来以分层方式组织您的代码。下面是一个示例

namespace Apresss
{
    namespace Data
    {
        public class DataManager
        {
        
        }
    }
    
    namespace IO
    {
        public class BluetoothReader
        {
        
        }
    }
}

可以通过完全限定的类型名称访问其中的项,如下面的示例所示

Apress.Data.DataManager dman = new Apress.Data.DataManager();

或者,通过使用 `using` 声明显式限定该命名空间中的项。下面是一个示例

using Apress.IO;
class Program
{
    static void Main(string[] args)
    {
        Apress.Data.DataManager dman = new Apress.Data.DataManager();
        BluetoothReader btooth = new BluetoothReader();
    }

}

在此上下文中,`using` 命令的唯一目的是节省您的输入并使您的代码更简单。例如,它不会将其他任何代码或库添加到您的项目中。除了允许我们在没有限定的情况下访问给定命名空间中的类型之外,`using` 关键字还可以为我们提供创建命名空间别名的能力。下面是一个示例

using aio = Apress.IO;

以及提供类型别名的能力,如下面的示例所示

using Apress.IO;
using aio = System.Console;
class Program
{
    static void Main(string[] args)
    {
        aio.WriteLine("Aliased Type."); 
    }

}

在上面的示例中,类型 `System.Console` 被赋予了一个新名称 `aio`。程序 `Main` 方法中的代码现在可以通过此别名访问 `Console` 的成员。

别名最常见的用法是当两个库之间发生名称冲突,或者当正在使用一个大得多命名空间中的少量类型时。请检查下面的示例列表

namespace Apress
{
    namespace Data
    {
        public class DataManager
        {
        
        }
    
        public class BluetoothReader
        {
        
        }
    }
    
    namespace IO
    {
        public class BluetoothReader
        {
        
        }
    }
}

命名空间 `Apress.Data` 和 `Apress.IO` 具有相同名称的类型。应用 `using` 指令来访问这两个命名空间中的类型成员,然后尝试实例化 `BluetoothReader` 将会产生一个无法解决的歧义,因此下面的代码将无法编译

using Apress.IO;
using Apress.Data;

class Program
{
    static void Main(string[] args)
    {
        BluetoothReader bt = new BluetoothReader(); 
    }

}

别名有什么新变化?

在上面的示例中,别名命名空间似乎是一个恰当的解决方案。该示例再次说明了它是如何工作的

using apressio = Apress.IO;
using apresssdata = Apress.Data;

class Program
{
    static void Main(string[] args)
    {
        apressio.BluetoothReader bt = 
             new apressio.BluetoothReader(); 
    }

}

不幸的是,有时这种做法会产生错误。当引用库中的命名空间名称与我们给定的别名名称发生冲突时,问题就会显现出来。下面的示例说明了这个问题

namespace apressio
{
    public class TypeB
    {
    
    }
}

namespace apressdata
{
    public class TypeA
    {
    
    }
}

namespace Apress
{

    namespace Data
    {
        public class DataManager
        {
        
        }
        
        public class BluetoothReader
        {
        
        }
    }
    
    namespace IO
    {
        public class BluetoothReader
        {
        
        }
    }
}

在上面的示例中,添加了两个新的命名空间 `apressio` 和 `apressdata`,因为它们与我们的别名共享相同的名称,所以代码将不允许编译。解决此问题的快速方法是将别名定义移出模块范围,并移入它们所使用的命名空间。下面的示例说明了最初的问题

using apressio = Apress.IO;
using apressdata = Apress.Data;


namespace apressio
{
    public class TypeB
    {
    
    }
}

namespace apressdata
{
    public class TypeA
    {
    
    }
}

namespace Apress
{

    namespace Data
    {
        public class DataManager
        {
        
        }
        
        public class BluetoothReader
        {
        
        }
    }
    
    namespace IO
    {
        public class BluetoothReader
        {
        
        }
    }
}


namespace Aliases
{

    class Program
    {
        static void Main(string[] args)
        {
        
            //apressio.Bluetooth
            apressio.BluetoothReader bt = 
               new apressio.BluetoothReader();
        }
    }
}

在这里,别名 `apressio` 和 `apressdata` 定义在 C# 文件开头的典型位置。尝试编译此代码将失败。为了在不引入任何新技术的情况下使其编译,只需将声明移到它们使用的命名空间内部。现在,`Aliases` 命名空间定义如下

namespace Aliases
{
    using apressio = Apress.IO;
    using apressdata = Apress.Data;
    
    class Program
    {
        static void Main(string[] args)
        {
        
            //apressio.Bluetooth
            apressio.BluetoothReader bt = 
               new apressio.BluetoothReader();
        }
    }
}

这种策略带来了它自己的问题;总而言之,我们发现新的命名空间在该范围内不再可见。C# 2.0 中引入的命名空间别名限定符是给定场景的适当措施。下面的示例显示了如何实现这一点

using apressio = Apress.IO;
using apressdata = Apress.Data;
using aio = apressio;

namespace apressio
{
    public class TypeB
    {
    
    }
}

namespace apressdata
{
    public class TypeA
    {
    
    }
}

namespace Apress
{
    namespace Data
    {
        public class DataManager
        {
        
        }
    
        public class BluetoothReader
        {
        
        }
    }
    
    namespace IO
    {
        public class BluetoothReader
        {
        
        }
    }
}

namespace Aliases
{
    class Program
    {
        static void Main(string[] args)
        {
            apressio::BluetoothReader bt = 
                new apressio::BluetoothReader();
            aio::TypeB tb = new aio::TypeB();
        }
    }
}

命名空间别名限定符可以按如下方式使用

<Left operand> ::  < right operand>

其中左操作数可以是命名空间别名、`extern` 或全局标识符。右操作数必须是类型。将命名空间别名限定符与引用类型的别名一起使用会导致编译时错误。

global

global 不是关键字或标识符,但当与命名空间别名限定符结合使用时,只会搜索 `global` 命名空间以查找右侧标识符。`global` 标识符可以按如下方式使用。

global::apressio.TypeB b = new apressio.TypeB(); //use of global
global::System.IO.DirectoryInfo info = 
                  new System.IO.DirectoryInfo("c:/");

extern

信不信由你,问题仍然潜伏在暗处。常识和严格的编译器阻止我们创建如下定义的场景,其中两个不同的类型在同一程序集中共享相同的限定名称

namespace apressio
{
    public class TypeB
    {
    
    }
}

namespace apressio
{
    public class TypeB
    {
    
    }
}

然而,没有任何东西可以阻止在两个不同的程序集中出现这种情况。

//Apress1.dll
namespace apressio
{
    public class TypeB
    {
    
    }
}

Apress2.dll
namespace apressio
{
    public class TypeB
    {
    
    }
}

当然,这不会成为问题,直到我们遇到同时引用这两个程序集的情况。由于所有引用的程序集类型以及运行程序中的所有类型都被加载到同一个命名空间层次结构中,当我们尝试构建我们的消费程序时,一切都会崩溃。现代编程世界中,上述场景是一个非常现实的问题,哪怕只是因为伟大的头脑思维一致。(引用同一程序集的多个版本也会导致这种情况)。幸运的是,随着 C# 2.0 的发布,有了通过 `extern` 关键字和编译时配置实现的用于处理多个命名空间层次结构的功能。

利用 `extern` 别名是一个两步过程。第一步是使用 `extern` 别名关键字在代码中声明层次结构。下面的示例说明了这一点。

extern alias ApressLibrary1;
extern alias ApressLibrary2;

`extern` 别名声明前面不能有任何其他内容,所以它们应该放在要使用它们的文件顶部。

extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using apressio = Apress.IO;
using apressdata = Apress.Data;
using aio = apressio;

namespace Aliases
{
    class Program
    {
        static void Main(string[] args)
        {
            //code that references two seperate TypeB 
            //definitions
            //will go here
        }
    }
}

下一步可以通过两种方式完成。从命令行,我们将如下在编译期间定义别名

[编辑注释:使用换行符以避免滚动。]

csc /r:ApressLibrary1= ApressLibrary.dll /
   r: ApressLibrary2= ApressLibrary2.dll Aliases.cs

也可以使用 Visual Studio 2005 集成开发环境通过修改引用程序集的 `Aliases` 属性来实现此步骤,该属性始终默认为 `global`,对于 *ApressLibrary1.dll* 和 *ApressLibrary2.dll* 分别为 `ApressLibrary1` 和 `ApressLibrary2`。通过在选择引用程序集时查看标准属性窗口,可以访问引用程序集的属性选项卡。下图显示了引用程序集的初始别名

对于 `ApressLibrary1`,属性选项卡将被修改为这样

对于 `ApressLibrary2`,属性选项卡将被修改为这样

现在我们有了两个可以唯一访问的根命名空间层次结构。下面的代码说明了它们的使用

extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Aliases
{
    class Program
    {
        static void Main(string[] args)
        {
            ApressLibrary1::apressio.TypeB typeB = 
                    new ApressLibrary1::apressio.TypeB();
            ApressLibrary2::apressio.TypeB typeB2 = 
                    new ApressLibrary2::apressio.TypeB();
        }
    }
}

上面的示例定义了 `extern` 别名 `ApressLibrary1` 作为由 *ApressLibrary1.dll* 中的类型形成的命名空间层次结构的根,并将 `ApressLibrary2` 定义为由 *ApressLibrary2.dll* 中的类型形成的命名空间层次结构的根。现在可以使用命名空间限定符语法通过 `TypeB` 访问 `TypeB`,而不会发生名称冲突。

虽然命令行别名声明是通过多次使用 /r 开关实现的(例如,要引用同一程序集但具有多个别名),但 Visual Studio 2005 通过在 `Aliases` 属性中接受逗号分隔的字符串来允许使用多个别名。然后,我们可以将一个程序集放在 `global` 命名空间以及 `extern` 别名中,并以任一方式访问它。在下面的示例中,程序集 *ApressLibrary2.dll* 被引用到 `global` 命名空间以及 `ApressLibrary2` 命名空间中。

*apressLibrary2.dll* 中的 `TypeB` 现在可以通过 `ApressLibrary2` `extern` 别名或通过 `global` 命名空间别名访问。下面的示例说明了这一点

extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Aliases
{
    class Program
    {
        static void Main(string[] args)
        {
            ApressLibrary1::apressio.TypeB typeB = 
                  new ApressLibrary1::apressio.TypeB();
            ApressLibrary2::apressio.TypeB typeB2 = 
                  new ApressLibrary2::apressio.TypeB();
            apressio.TypeB typeB3 = new apressio.TypeB(); 
        }
    }
}

一旦定义了 `extern` 别名,它就可以像任何标准命名空间一样使用。下面的示例说明了这一点

extern alias ApressLibrary1;
extern alias ApressLibrary2;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Aliases
{
    using ApressLibrary1::Apress.Data;
    using io = ApressLibrary2::apressdata.TypeA;
    class Program
    {
        static void Main(string[] args)
        {
            ApressLibrary1::apressio.TypeB typeB = 
                   new ApressLibrary1::apressio.TypeB();
            ApressLibrary2::apressio.TypeB typeB2 = 
                   new ApressLibrary2::apressio.TypeB();
            apressio.TypeB typeB3 = new apressio.TypeB();
            io typeA = new io();
            BluetoothReader blueReader = 
                            new BluetoothReader();
        }
    }
}
© . All rights reserved.