C# で Generics を触る必要があったので、しょぼーいプログラムを書いてみた。Generics を体にしみこませるためには、まだまだやけど、最初の一歩としては悪くないだろう。
C# の Generics に関しては非常にディープな解説がここにある。
素晴らしい。何も付け加えるものが無い。このブログはいつもそうだけど、自分がプログラミングの勉強用に書いているものなので、あまり、参考とかではないと思うけど、自分のために書きます。
ジェネリクスの目的
ジェネリクスの目的は型に依存しないコードを抽象化するというところだと思います。本来型の種類ごとに書かないといけない処理を、一つのコードで書くことができます。もちろん何にでも書けるわけではありません。
先ほどのブログを見ると、C# の Generics はかなりイケているらしく、コードによっては、インターフェイスを使った抽象化より、処理が高速だったりするケースもあるようです。今回、ジェネリクスを調べている動機は、先日から作っているコードで、どうしてもカッコいい API が作れないところがあったので、それを何とかしたいと、師匠に相談したら、ジェネリクスと、リフレクションを使った超カッコいい解決策をしめしてくれました。
単にマネするのではなく、師匠の教え通り、自分で試して簡単なブログを書いて、自分にしみこませてからコードを書こうと思っています。
はじめてのジェネリクス
ジェネリクスのコンセプトは、「知って」いましたが、利用する以外に自分がコードを書く場面がありませんでした。全然ちがった2つのクラスがあって、その属性が全部値が入ったら、Completed() メソッドが true になるというコードを書いてみます。対象のクラスの属性数は違うので、リフレクションは次にとってあるので、そのままだと、インターフェイスでプログラミングを考えてもできるのですが、強引にジェネリクスのコードを書いてみました。
C# では、default という、型に対して、デフォルト値をとるというメソッドがあるみたいなので使ってみます。参照型はnull 数値型は0になります。各属性をしらべて、値が未代入ということは、default になっているということだろうということで、default の値と、引き渡された値が違っていれば trueを返します。
ここでポイントは、where T: IComparable
という指定で、型はなんでもいいけど、IComparable インターフェイスを実装しているものという制約をつけています。それを where
で制約をかけれます。通常ジェネリクスだと、型に対してメソッド呼び出しできませんが、こうやって、制約をかけると、メソッド呼び出しができます。
ただ、このコードの場合は、結局 null があったら、うまく CompareTo() が動かないので、null チェックをしないといけないので、あまり default とか、CompareTo とかのうまみがありませんね。でも、最初のジェネリクスプログラムとして、各要素を学びました。
protected Boolean IsNotDefault<T>(T a) where T : IComparable {
if (a == null) return false;
var b = default(T);
return !(a.CompareTo(b) == 0);
}
全体では各クラスは、Base のスーパークラス内の、ジェネリックメソッド IsNotDefault(T a) を利用しています。各属性の数が違うので、今は手書きで、このメソッドを呼んでいます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericsAndReflection
{
public interface ICompleted
{
Boolean Completed();
}
public class Base : ICompleted
{
protected Boolean IsNotDefault<T>(T a) where T : IComparable {
if (a == null) return false;
var b = default(T);
return !(a.CompareTo(b) == 0);
}
public virtual bool Completed()
{
throw new NotImplementedException();
}
}
public class Foo: Base
{
public string ApplicationId { get; set; }
public string ApplicationSecret { get; set; }
public bool Completed()
{
return IsNotDefault<string>(ApplicationId) && IsNotDefault<string>(ApplicationSecret);
}
public string ToString()
{
return $"Foo: ApplicationId: {ApplicationId} ApplicationSecret: {ApplicationSecret} Completed: {Completed()}";
}
}
public class Bar : Base
{
public int Id { get; set; }
public string Name { get; set; }
public bool Completed()
{
return IsNotDefault<int>(Id) && IsNotDefault<string>(Name);
}
public string ToString()
{
return $"Bar: Id: {Id} Name: {Name} Completed: {Completed()}";
}
}
public class GenericsSample
{
public void exec()
{
var foo = new Foo();
foo.ApplicationId = "abc";
Console.WriteLine(foo.ToString());
foo.ApplicationSecret = "secret";
Console.WriteLine(foo.ToString());
var bar = new Bar();
bar.Id = 10;
Console.WriteLine(bar.ToString());
bar.Name = "Yamada";
Console.WriteLine(bar.ToString());
}
}
}
実行結果
Foo: ApplicationId: abc ApplicationSecret: Completed: False
Foo: ApplicationId: abc ApplicationSecret: secret Completed: True
Bar: Id: 10 Name: Completed: False
Bar: Id: 10 Name: Yamada Completed: True
バブルソートをしてみる
思いっきり車輪の再発明ですが、勉強のためにはいいでしょう。バブルソートをジェネリクスで自分で実装してみます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GenericsAndReflection
{
public class SomeSort
{
private List<T> Sort<T>(List<T> source) where T : IComparable<T>
{
List<T> result = new List<T>(source);
for (var i = 0; i < result.Count() - 1; i++)
{
for (var j = result.Count() - 1; j > i; j--)
{
if (result[j].CompareTo(result[j - 1]) < 0)
{
var a = result[j - 1];
result[j - 1] = result[j];
result[j] = a;
}
}
}
return result;
}
public void exec()
{
var num = new List<int>
{
5, 8, 10, 3 , 4
};
var result = Sort<int>(num);
foreach( var e in num)
{
Console.WriteLine($"Original: {e}");
}
foreach(var e in result)
{
Console.WriteLine($"Result: {e}");
}
var str = new List<string>
{
"Yamada", "Ushio", "Tsuda", "Ohta", "Caro", "Tesar",
};
var namelist = Sort<string>(str);
foreach (var e in str)
{
Console.WriteLine($"Original: {e}");
}
foreach (var e in namelist)
{
Console.WriteLine($"Result: {e}");
}
}
}
}
使っているのはさっきのテクニックを超えたものではありません。IComparable をもった要素のリストならソートしますという内容です。内容がスワップとかそんなレベルなので十分ジェネリクスでコーディングできます。実際にコードを書いているときは、最初は Select を使ったディープコピーをしようとしましたが、普通に考えてシャローコピーでいいのでこの方法に落ち着きました。そういう試行の過程がいいですね。
実行結果
Original: 5
Original: 8
Original: 10
Original: 3
Original: 4
Result: 3
Result: 4
Result: 5
Result: 8
Result: 10
Original: Yamada
Original: Ushio
Original: Tsuda
Original: Ohta
Original: Caro
Original: Tesar
Result: Caro
Result: Ohta
Result: Tesar
Result: Tsuda
Result: Ushio
Result: Yamada