ホームページ  >  記事  >  バックエンド開発  >  C# 2.0 仕様(二)

C# 2.0 仕様(二)

黄舟
黄舟オリジナル
2017-01-03 10:14:431067ブラウズ

19.1.5 ジェネリックメソッド

場合によっては、型パラメータはクラス全体に必要ではなく、特定のメソッドにのみ必要になります。多くの場合、これはジェネリック型をパラメーターとして受け入れるメソッドを作成する場合に当てはまります。たとえば、前述の Stack8742468051c85b06f0a0af9e3e506b5c クラスを使用する場合、複数の値を連続してプッシュするのが一般的なパターンですが、これを 1 回の呼び出しで行うメソッドを記述することも便利です。 Stackbd43222e33876353aff11e13a7dc75f6 などの特定の構築型の場合、メソッドは次のようになります。

void PushMultiple(Stack<int> stack ,params int[] values)
{
foreach(int value in values) 
stack.Push(value);
}

这个方法可以用于压入多个int值到一个Stack<int>中。

Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1,2, 3, 4);

ただし、前の方法は特定の構築型 Stackbd43222e33876353aff11e13a7dc75f6 に対してのみ有効です。これを Stack8742468051c85b06f0a0af9e3e506b5c で動作させるには、メソッドをジェネリック メソッドとして記述する必要があります。ジェネリック メソッドでは、メソッド名の後に「e541b52b254dc5f8a220aae37694ce5c」で区切られた 1 つ以上の型パラメータを指定します。型パラメーターは、パラメーター リスト、戻り値の型、およびメソッド本体内で使用できます。一般的な PushMultiple メソッドは次のようになります。

void PushMultiple<T>(Stack<>T stack , params T[] values)
{
foreach(T value in values) stack.Push(value);
}

この一般的なメソッドを使用すると、複数のアイテムを任意のスタック8742468051c85b06f0a0af9e3e506b5cにプッシュできます。ジェネリック メソッドを呼び出す場合、型パラメーターの値はメソッド呼び出し内で山括弧内に指定されます。たとえば、

Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack , 1,2,3,4);

この汎用 PushMultiple メソッドは、任意の Stack8742468051c85b06f0a0af9e3e506b5c で動作できるため、前のバージョンよりも再利用可能ですが、メソッドの型パラメータとして T を指定する必要があるため、呼び出すときに不便なようです。多くの場合、コンパイラは型推論と呼ばれるプロセスを使用して、メソッドに渡された他のパラメータから正しい型パラメータを推論します。前の例では、最初の仮パラメータは Stackbd43222e33876353aff11e13a7dc75f6 型で、後続のパラメータは int 型であるため、コンパイラは型パラメータ値が int である必要があると推測できます。したがって、ジェネリック PushMultiple メソッドを呼び出すときに型パラメーターを指定する必要はありません。

Stack<int> stack = new Stack<int>();
PushMultiple(stack , 1,2, 3, 4);

19.2 匿名メソッド

イベント ハンドラーやその他のコールバック関数は、多くの場合、直接ではなく、特殊なデリゲートを通じて呼び出す必要があります。それでも、イベント ハンドラーとコールバック関数のコードを特定のメソッドに配置し、このメソッドのデリゲートを明示的に作成することしかできません。対照的に、匿名メソッドを使用すると、デリゲートに関連付けられたコードを、デリゲートを使用するローカル メソッド内でインラインで記述することができるため、コードがデリゲート インスタンスに直接送られるのが便利です。この利便性に加えて、匿名メソッドはローカル ステートメントに含まれる関数メンバーへのアクセスを共有します。名前付きメソッドを (匿名メソッドと区別して) 共有するには、補助クラスを手動で作成し、ローカル メンバーをクラスのドメインに「リフト」する必要があります。

以下の例は、リスト ボックス、テキスト ボックス、ボタンを含む単純な入力フォームを示しています。ボタンを押すと、テキストボックス内の文字を含む項目がリストボックスに追加されます。

class InputForm:Form
{
ListBox listBox;
TextBox textbox;
Button addButton;
pubic MyForm()
{
listBox = new ListBox(…);
textbox = new TextBox(…);
addButon = new Button(…);
addButton.Click += new EventHandler(AddClick);
}
void AddClick(object sender , EventArgs e)
{
listBox.Items.Add(textbox.Text);
} 
}

ボタンの Click イベントに応じて実行する必要があるステートメントは 1 つだけですが。このステートメントは、完全なパラメーター リストを含む別のメソッドにも配置する必要があり、そのメソッドを参照する EventHandler デリゲートも手動で作成する必要があります。匿名メソッドを使用すると、イベント処理コードが非常に簡潔になります。
class InputForm:Form
{
ListBox listBox;
TextBox textbox;
Button addButton;
pubic MyForm()
{
listBox = new ListBox(…);
textbox = new TextBox(…);
addButon = new Button(…);
addButton.Click +=delegate{
listBox.Items.Add(textBox.Text.);
}
}

匿名メソッドは、キーワード デリゲートとオプションのパラメーター リスト、および "{" と "}" 区切り文字で囲まれたステートメントで構成されます。前の例では、匿名メソッドはデリゲートによって提供されたパラメーターを使用しなかったため、パラメーター リストは省略されました。パラメータにアクセスする場合は、匿名メソッドにパラメータ リストを含めることができます。

addButton.Click += delegate(object sender , EventArgs e){
MessageBox.Show(((Button)sender).Text);
};

在前面的例子中,将会发生一次从匿名方法到EventHandler委托类型(Click事件的类型)的隐式转换。这种隐式转换是可能的,因为参数列表和委托类型的返回值与匿名方法是兼容的。关于兼容性的确切规则如下:

如果下列之一成立,那么委托的参数列表与匿名方法是兼容的。

- 匿名方法没有参数列表,并且委托没有out 参数。
- 匿名方法包含的参数列表与委托的参数在数量、类型和修饰符上是精确匹配的。

如果下列之一成立,那么委托的返回类型与匿名方法兼容。

- 委托的返回类型是void,匿名方法没有返回语句,或者只有不带表达式的return 语句。
- 委托的返回类型不是void ,并且在匿名方法中,所有return 语句相关的表达式可以被隐式转换到委托的类型。

在委托类型的隐式转换发生以前,委托的参数列表和返回类型二者都必须与匿名方法兼容。

下面的例子使用匿名方法编写了“内联”函数。匿名方法被作为Function委托类型而传递。

using System;
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a ,Function f)
{
double[] result = new double[a.Length];
for(int i=0;i<a.Length;i++) result[i] = f(a[i]);
return result;
}
static double[] MultiplyAllBy(double[] a, double factor)
{
return Apply(a, delegate(double x){return x*factor;})
}
static void Main()
{
double[] a = {0.0,0.5,1.0};
double[] squares = Apply(a, delegate(double x){return x*x});
double[] doubles = MultiplyAllBy(a , 2.0);
}
}

Apply方法适用一个 double[]元素的给定的Function,并返回一个double[]作为结果。在Main方法中,传递给Apply的第二个参数是一个匿名方法,它与Fucntion委托类型兼容。该匿名方法只是返回参数的平方,而Apply调用的结果是一个double[] ,在a中包含了值的平方。

MultiplyAllBy方法返回一个由一个给定factor(因数)的在参数数组a中的每个值相乘而创建的double[] 。要得到结果,MultiplyAllBy调用了Apply方法,并传给它一个匿名方法(在参数上乘以因数factor)。

如果一个本地变量或参数的作用域包括了匿名方法,则该变量和参数被称为匿名方法的外部变量(outer variable)。在MultiplyAllBy方法中,a和factor是传递给Apply的匿名方法的外部变量,因为匿名方法引用了factor,factor被匿名方法所捕获(capture)[/b]。通常,局部变量的生存期被限制在它所关联的块或语句的执行区。然而,被捕获的外部变量将一直存活到委托所引用的匿名方法可以被垃圾回收为止。

19.2.1方法组转换

如前面所描述的,匿名方法可以被隐式地转换到与之兼容的委托类型。对于一个方法组,C#2.0允许这种相同类型的转换,即在几乎任何情况下都不需要显式地实例化委托。例如,下面的语句

addButton.Click += new EventHandler(AddClick);
Apply(a , new Function(Math.Sin));

可以被如下语句代替.

addButton.Click += AddClick;
Apply(a , Math.Sin);

当使用这种简短的形式时,编译器将自动推断哪一个委托类型需要实例化,但其最后的效果与较长的表达形式是一样的。

19.3迭代器

C#的foreach语句被用于迭代一个可枚举(enumerable)集合的所有元素。为了可以被枚举,集合必须具有一个无参数GetEnumerator方法,它返回一个enumertor(枚举器)。一般情况下,枚举器是很难实现的,但这个问题使用迭代器就大大地简化了。

迭代器是一个产生值的有序序列的语句块。迭代器不同于有一个或多个yield语句存在的常规语句块。

yield return语句产生迭代的下一个值。 
yield break语句指明迭代已经完成。

只要函数成员的返回类型是枚举器接口(enumerator interface)或可枚举接口(enumerable interface)之一,迭代器就可以被用作函数体。

枚举器接口是System.Collections.IEnumerator和由Sysetm.Collections.Generic.IEnumerator8742468051c85b06f0a0af9e3e506b5c所构造的类型。 
可枚举接口是System.Collections.IEnumerable和由System.Collections.Generic.IEnumerable8742468051c85b06f0a0af9e3e506b5c构造的类型。


迭代器不是一种成员,它只是实现一个函数成员的方式,理解这点是很重要的。一个通过迭代器被实现的成员,可以被其他可能或不可能通过迭代器而被实现的成员覆盖和重载。

下面的Stack8742468051c85b06f0a0af9e3e506b5c类使用迭代器实现了它的GetEnumerator方法。这个迭代器依序枚举了堆栈从顶到底的所有元素。

using System.Collections.Generic;
public class Stack<T>:IEnumerable<T>
{
T[] items;
int count;
public void Push(T data){…}
public T Pop(){…}
public IEnumerator<T> GetEnumerator()
{
for(int i =count-1;i>=0;--i){
yield return items[i];
}
}
}

GetEnumerator方法的存在使得Stack8742468051c85b06f0a0af9e3e506b5c成为一个可枚举类型,它使得Stack8742468051c85b06f0a0af9e3e506b5c的实例可被用在foreach语句中。下面的例子压入从0到9 的值到一个整数堆栈中,并且使用一个foreach循环依序显示从堆栈顶到底的所有值。

using System;
class Test
{
static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i=0;i<10;i++) stack.Push(i);
foreach(int i in stack) Console.Write(“{0}”,i);
Console.WriteLine();
}
}

例子的输出入下:

9 8 7 6 5 4 3 2 1 0

foreach语句隐式地调用了集合的无参数GetEnumerator方法以获得一个枚举器。由集合所定义的只能有一个这样的无参数 GetEnumerator方法,但经常有多种枚举方式,以及通过参数控制枚举的方法。在这种情况下,集合可以使用迭代器实现返回可枚举接口之一的属性和方法。例如,Stack8742468051c85b06f0a0af9e3e506b5c可能引入两个IEnumerable8742468051c85b06f0a0af9e3e506b5c类型的新属性,TopToBottom和BottomToTop。

using System.Collections.Generic;
public class Stack<T>: IEnumerable<T>
{
T[] items;
int count;
public void Push(T data){…}
public T Pop()P{…}
public IEnumerator<T> GetEnumerator()
{
for(int i= count-1;i>=0;--i)
{ 
yield return items[i];
}
}
public IEnumerable<T> TopBottom{
get{
return this;
}
}
public IEnumerable<T> BottomToTop{
get{
for(int I = 0;i<count;i++)
{
yield return items[i];
}
}
}
}

TopToBottom属性的get访问器只是返回this,因为堆栈自身是可枚举的。BottomToTop属性返回一个使用C#迭代器实现的枚举。下面的例子展示了,属性如何用于枚举堆栈元素。

using System;
class Test
{
static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i = 0 ;i<10 ;i++) stack.Push(i);
for(int i in stack..TopToBottom) Console.Write(“{0}”,i);
Console.WriteLine();
for(int i in stack..BottomToTop) Console.Write(“{0}”,i);
Console.WriteLine();
} 
}

当然,这些属性同样也可以在foreach语句之外使用。下面的例子将调用属性的结果传递给了一个单独的Print方法。该例子也展示了一个用作FromToBy接受参数的方法体的迭代器。

using System;
using System.Collections.Generic;
class Test
{
static void Print(IEnumerable<int> collection)
{
foreach(int i in collection) Console.Write(“{0}”,i);
Console.WriteLine();
}
static IEnumerable<int> FromToBy(int from ,int to , int by)
{
for(int i =from ;i<=to ;i +=by)
{
yield return I;
}
}

static void Main()
{
Stack<int> stack = new Stack<int>();
for(int i= 0 ;i<10;i ++) stack.Push(i);
Print(stack.TopToBottom);
Print(stack.BottomToTop);
Print(FromToBy(10,20,2));
}
}

该例子的输出如下。

9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
10 12 14 16 18 20

泛型和非泛型可枚举接口包含一个单一的成员,一个不接受参数的GetEnumerator方法 ,它一个枚举器接口。一个枚举充当一个枚举器工厂。每当调用了一个正确地实现了可枚举接口的类的GetEnumerator方法时,都会产生一个独立的枚举器。假定枚举的内部状态在两次调用GetEnumerator之间没有改变,返回的枚举器应该产生相同集合相同顺序的枚举值。在下面的例子中,这点应该保持,即使枚举的生存期发生交叠。

using System;
using System.Collections.Generic;
class Test
{
static IEnumerable<int> FromTo(int from , int to)
{
while(from<=to) yield return from++;
}

static void Main()
{
IEnumerable<int> e = FromTo(1,10);
foreach(int x in e)
{
foreach(int y in e)
{
Console.WriteLine(“{0,3}”,x*y);
}
Console.WriteLine();
}
}
}

先前的代码打印了整数1到10 的乘法表。注意,FromTo方法只被调用了一次用来产生枚举e。然而,e.GetEnumerator()被调用了多次(通过foreach语句),以产生多个等价的枚举器。这些枚举器都封装了在FromTo声明中指定的迭代器代码。注意迭代器代码修改了from参数。

不过,枚举器是独立地运作的,因为每个枚举器都给出 from 和to它自己的拷贝。枚举器之间过渡状态的共享是众多细微的瑕疵之一,当实现枚举和枚举器时应该避免。C#迭代器的设计可用于避免这些问题,从而以一种简单直观地方式实现健壮的枚举和枚举器。

以上就是C# 2.0 Specification(二)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。