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

C# 扩展方法、命名参数、可选参数、对象初始化器和匿名类型的初学者教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (21投票s)

2013 年 4 月 5 日

CPOL

9分钟阅读

viewsIcon

51183

downloadIcon

335

在本文中,我们将讨论 C# 编程语言的四个非常重要的特性。

引言

在本文中,我们将讨论 C# 较新版本中引入的四个新颖且非常重要的特性。来自 C# 2.0 等旧版本的开发者,或者正在学习 C# 的人可能会觉得本文很有用。

背景

C# 是一门发展速度非常快的语言。每个新版本都增加了越来越多的特性。它比 C++ 和 Java 等语言晚出现多年,但在语言特性和开发人员生产力方面设法超越了它们。

在这个简短的教程文章中,我将尝试用我自己的方式来解释 C# 编程语言的四个非常重要的特性。这对于已经了解这些特性的读者可能不太有用,但本文是为那些仍在学习 C# 的初学者准备的。

在本文中,我们将讨论 C# 的以下四个特性。我们还将为每个特性编写一个示例应用程序,以便直观地了解它们。

  • 扩展方法
  • 命名参数和可选参数
  • 对象初始化器
  • 匿名类型

Using the Code

扩展方法

开闭原则 (OCP) 指出,我们应该以这样一种方式设计我们的类型,即它们应该是对扩展开放的,但对修改关闭的。C# 中的扩展方法可以被视为一种机制,用于为用户定义的类型甚至原始类型和框架中定义的类型实现 OCP

扩展方法允许我们通过向现有类型添加额外的方​​法和功能来扩展现有类型,而无需更改该类型的代码(在大多数情况下我们甚至可能没有该类型的代码)。在扩展方法出现之前,开发人员会创建自己的类型,然后通过继承或在这些类型中包含现有类型来使用它们。这些新类型更像是现有类型的 包装器,而不是这些类型的实际扩展。

例如,如果我们想获得一个整数的负值的功能,那么我将不得不将我的整数包装在一个自定义类型中,然后使用这个自定义类型来执行操作。

// Old way of extending using wrapper classes
struct MyInt
{
    int value;       

    public MyInt(int val)
    {
        this.value = val;
    }

    public int Negate()
    {
        return -value;
    }
}

static void Main(string[] args)
{
    // Old way of using wrappers for extensions
    MyInt i = new MyInt(53);
    Console.WriteLine(i.Negate());
}

现在这种方法确实可行,但问题在于我们并没有真正扩展现有类型。我们创建了一个全新的类型,它包装了现有类型,然后为我们提供了所需的功能。

因此,如果我们真的想扩展一个方法,我们应该能够将这个 Negate 方法直接添加到 int 类型中,并且每个人都应该能够只在 int 类型上调用此方法。无需创建另一个类型来获得此附加功能。扩展方法正是提供了这一点。使用扩展方法,我们可以编写自定义功能,并将它们挂接到现有类型上,以便可以在这些类型上使用它们。

要创建扩展方法,我们需要执行以下步骤:

  1. 定义一个 static 类。
  2. 在该类中定义一个 public static 函数,其名称是所需的新方法名称,返回值根据功能而定。
  3. 传入您想要扩展的类型的参数。这里重要的是在参数前加上 this 关键字,以告知编译器我们需要扩展此类型,并且我们实际上不将此类型作为参数传递。

所以,让我们尝试将这个 Negate 函数添加到 int 类型中。

// Extending using Extension methods
static class MyExtensionMethods
{
    public static int Negate(this int value)
    {
        return -value;
    }
}

static void Main(string[] args)
{
    //Using extension method
    int i2 = 53;
    Console.WriteLine(i.Negate());
}

现在,使用扩展方法,让我们在某些现有类型上定义方法。那么,如果我们希望新方法接受一些参数呢?为了做到这一点,我们可以在要扩展的类型参数(与 this 关键字一起使用)之后定义其他参数。让我们在 int 中再定义一个名为 Multiply 的函数,以便看到这一点。

// Extending using Extension methods
static class MyExtensionMethods
{
    public static int Negate(this int value)
    {
        return -value;
    }

    public static int Multiply(this int value, int multiplier)
    {
        return value * multiplier;
    }
}

static void Main(string[] args)
{
    // Passing arguments in extension methods
    int i3 = 10;
    Console.WriteLine("Passing arguments in extension methods: {0}", i3.Multiply(2));
}

现在,在实现扩展方法之前,需要记住两点:

  • 第一点是,扩展方法只能访问类型的 public 属性。
  • 扩展方法的签名不应与该类型现有的方法签名相同。
  • 只有当包含扩展方法的命名空间在作用域内时,才能使用类型的扩展方法。
  • 如果我们定义的扩展方法重载了原始类型现有方法的签名,并且调用出现了歧义,那么重载解析规则将始终选择实例方法而不是扩展方法。
  • 如果两个扩展方法之间存在歧义,那么包含更具体参数的方法将被调用。

如果我们牢记以下几点,我们就可以真正利用扩展方法来更好地设计和扩展类型,并提供更好的类型抽象。LINQ 大量使用扩展方法。强烈建议查看 LINQ 如何与 LAMBDA 表达式结合使用扩展方法。

注意:有关 Lambda 表达式的信息可以在这里找到:C# 委托、匿名函数和 Lambda 表达式基础知识的初学者教程[^]。

可选参数

现在,在我们继续讨论命名参数和位置参数之前,让我们先谈谈可选参数。在旧版本的 C# 中,不可能为函数创建可选参数,也就是说,如果我需要为函数中的参数设置默认值,唯一的办法就是函数重载。

假设我们要定义一个 Multiply 函数,调用者可以传递两个数字并获得结果。但是,如果用户只想传递一个参数,这也是允许的,函数会将这个数字乘以 1 并返回值。现在,在旧版本的 C# 中,这是通过函数重载完成的,如下所示:

private static int Multiply(int num1, int num2)
{
    return num1 * num2;
}

private static int Multiply(int num1)
{
    return Multiply(num1, 1);
}

static void Main(string[] args)
{
    // Testing OldWay of default parameters
    Console.WriteLine(Multiply(2));
}

现在这提供了所需的结果,但问题在于,随着函数参数数量的增加,重载函数的数量也会随之增加,以提供所有可能的组合。拥有大量重载函数版本会给维护带来噩梦。

现在,为了解决这个问题,C# 引入了在函数中提供可选参数的机制。所以,使用可选参数,上面的函数将如下所示:

private static int Multiply2(int num1, int num2 = 1)
{
    return num1 * num2;
}

static void Main(string[] args)
{
    // Testing default parameters
    Console.WriteLine(Multiply2(2));
}

拥有可选参数的唯一限制是,默认参数应该始终放在函数所有必需参数之后。

命名参数

现在,拥有可选参数的可能性非常棒,但有一个小问题,如果函数中有多个可选参数。让我们看一个这样的函数:

private void TestFunction(int one, int two, int three = 3, int four = 4, int five = 5)
{
    // Some implementation here
}

现在,我们想传递 fourfive 的值,但想使用 three 的默认值。在正常情况下这是不可能的。为了执行此操作,我们需要使用命名参数。命名参数使我们有可能使用其名称传递特定的函数参数。所以,让我们尝试调用这个函数。

TestFunction(one: 11, two: 22, four: 44, five: 55);

现在像上面这样调用函数,我们使用了 three 的默认值,同时为 fourfive 传递了值,它们是定义在 three 之后的可选参数。

我们还为变量 onetwo 使用了命名参数,但由于我们按相应参数位置传递 onetwo,因此可以省略它们的名称,因此它们将仅作为位置参数传递,而 fourfive 作为命名参数。

TestFunction(11, 22, four: 44, five: 55);

对象初始化器

C# 中的对象初始化器为我们提供了一种简单的方法,可以在构造对象时初始化对象的所有属性。假设我们有一个简单的类,其中包含一些 public 属性,如下所示:

// A simple class to test object initializers
class Book
{
    public int BookID { get; set; }
    public string BookName { get; set; }
    public string AuthorName { get; set; }
    public string ISBN { get; set; }
}

现在,让我们使用对象初始化器创建这个类的对象,如下所示:

static void Main(string[] args)
{
    // using object initializers
    Book book = new Book
                {
                    BookID = 1,
                    BookName = "MVC Music Store Tutorial",
                    AuthorName = "Jon Galloway",
                    ISBN = "NA"
                };
}

关于自动属性的说明

现在,我定义类属性的方式可能对于使用 C# 2.0 或更早版本的用户来说是新的。这些属性被定义为自动属性。如果我们的类的任何变量仅用作数据存储,并且在设置或获取它之前没有任何验证逻辑,那么我们可以完全跳过创建变量并直接创建属性。编译器将在运行时负责为该属性生成一个变量。

隐式类型变量和匿名类型

在 C# 3.0 及更高版本中,我们可以定义一个变量而不指定其实际类型,前提是该变量正在被赋值。这些变量是隐式类型的,也就是说,变量的类型是分配给它的变量的类型。

var var1 = new Dictionary<string, Book>();
var var2 = "some random string";
var var3 = Multiply(3, 4);

现在 var1 的类型是 Dictionary<string, Book>,因为它就是这样创建的。var2 的类型是 string,因为分配给它的是一个 string,而 var3 的类型是 int,因为函数返回的是 int

var 并不定义一个可以赋任何类型的动态变量。它只是一个语法糖,这样我们就可以在局部作用域中跳过定义变量的类型。

var 只是一个语法糖。尽管如此,还有另一件重要的事情。我们应该尽量不要使用 var 关键字来声明局部变量,因为对于这些变量,我们可以定义显式类型。原因在于它降低了代码的可读性。那么问题来了,什么时候应该使用 var 呢?

我们应该只在必须时才使用 var。唯一需要使用 var 的地方是与匿名类型相关,即在运行时即时创建且其类型名称在编译时不可用的类型。LINQ 查询经常需要创建匿名类型,这是我们应该使用隐式类型变量,即 var 的唯一地方。

让我们创建一个简单的匿名类型,并使用隐式类型变量对其进行赋值和使用。

// Mandatory use of var, i.e., anonymous types
var aVar = new { Name = "Rahul", Age = 30 };
Console.WriteLine("{0} is {1} years of age", aVar.Name, aVar.Age);

注意:匿名类型强制要求使用 var 关键字。对于所有其他场景,由于上述可读性降低的问题,不建议使用 var。如果使用了 LINQ 投影,那么使用 var 关键字是绝对必要的。

看点

在本文中,我们讨论了 C# 3.0 中引入的一些特性。本文是为完全不了解这些 C# 特性的初学者准备的,并且完全是从初学者的角度编写的。我强烈建议您查看代码以全面了解所有这些特性。希望本文内容丰富。

历史

  • 2013年4月5日:第一个版本
© . All rights reserved.