C#生命体の実現を目指して
自分自身と同じソースコードをコードに作成させる
一か月ほど前、C# Advent Calendar 2011に一か月もあれば何か思いつくだろうと参加表明したのはいいものの、結局いいネタが思い浮かばずに、順番がまわってきてしまいました。。
23日担当のIIJIMASです。このエントリをC# Advent Calendar 201123日目とします。
とりあえず、苦し紛れに、思いついたネタがこれです。役に立つ知識だとか、最新の話題でなくて申し訳ございません。
人間を含む生物は、遺伝子という設計図があります。遺伝子は生物の「中」にあります。細胞にという生物個体のごく小さな一部にあります。考えてみると、とても不思議な気もします。ごく一部分に全体の設計図があるのです。マトリョーシカ人形のようなイメージでいえば、中から出てくるのは外の個体より一回り小さくなっているのが直観的な気がします。実際にはそうではないので、生命の遺伝はとても興味深い現象だと思います。
そこで「コンピュータプログラムで生命のようなものができると面白い」と考えました。
まず、自己複製を考えようと思います。ファイルコピーを使えばすぐできてしまいますが、あえてファイルコピーではなくあくまでもそのプログラムで同じコードを書き出すプログラムを作成してみました。
using System; using System.IO; namespace SelfGenarationCode { /// <summary> /// 自分自身と同じ内容のコードを出力するプログラム /// </summary> class Program { const string Code1 = @"using System; using System.IO; namespace SelfGenarationCode { /// <summary> /// 自分自身と同じ内容のコードを出力するプログラム /// </summary> class Program {"; const string Code2 = @" /// <summary> /// エントリポイント /// </summary> static void Main(string[] args) { GenerateNext(""SelfGenarationCodeProgram.cs""); } /// <summary> /// 文字列にするために文字列の""を""""に変換する /// </summary> public static string ConvertCode(string source) { string result = source.Replace(""\"""", ""\""\""""); return ""@\"""" + result + ""\""""; } /// <summary> /// 次のC#コードを出力する /// </summary> public static void GenerateNext(string fileName) { using (StreamWriter sw = new StreamWriter(fileName)) { sw.WriteLine(Code1); sw.WriteLine("" const string Code1 = {0};"", ConvertCode(Code1)); sw.WriteLine("" const string Code2 = {0};"", ConvertCode(Code2)); sw.WriteLine(Code2); } Console.WriteLine(""{0}を作成しました。"",fileName); Console.ReadKey(); } } }"; /// <summary> /// エントリポイント /// </summary> static void Main(string[] args) { GenerateNext("SelfGenarationCodeProgram.cs"); } /// <summary> /// 文字列にするために文字列の"を""に変換する /// </summary> public static string ConvertCode(string source) { string result = source.Replace("\"", "\"\""); return "@\"" + result + "\""; } /// <summary> /// 次のC#コードを出力する /// </summary> public static void GenerateNext(string fileName) { using (StreamWriter sw = new StreamWriter(fileName)) { sw.WriteLine(Code1); sw.WriteLine(" const string Code1 = {0};", ConvertCode(Code1)); sw.WriteLine(" const string Code2 = {0};", ConvertCode(Code2)); sw.WriteLine(Code2); } Console.WriteLine("{0}を作成しました。",fileName); Console.ReadKey(); } } }
実に単純なプログラムですね。リテラル文字列が遺伝子といったところでしょうか。
手動でコンパイルして出力されたexeを実行します。
csc SelfGenarationCodeProgram.cs SelfGenarationCodeProgram.exe
すると、SelfGenarationCodeProgram.csが同じ内容で上書きされます(元のファイルをリネームしておかないと上書きされます。更新日時が変わるはずです。)
確かに、元と全く同じソースコードを出力できました。dfやWinMergeで確かめてみてください。
しかし、手動でexeを作成するのが面倒です。実際の生物でも次の世代を作るのに神に手をわずらわさせたりはしないと思います。
実行ファイルもコードに作成させる
そこで、コンパイルや実行もプログラム自分自身にさせようと思います。ただし、実行中のexeファイルを上書きはできないようなので、毎回、ファイルの後ろに番号を増加させて別のファイルにします。また、「実行」を完全に自動にしてしまうと、物理リソースを食いつぶすまでの無限増殖になってしまうので、[Y]キーを押した時だけ「実行」するようにします。
SelfGenarationCode.cs
using System; using System.IO; using Microsoft.CSharp; using System.CodeDom.Compiler; using System.Diagnostics; using System.Text.RegularExpressions; namespace SelfGenarationCode { /// <summary> /// 自分自身のソースコードと実行ファイルを作成するプログラム /// </summary> class Program { const string Code1 = @"using System; using System.IO; using Microsoft.CSharp; using System.CodeDom.Compiler; using System.Diagnostics; using System.Text.RegularExpressions; namespace SelfGenarationCode { /// <summary> /// 自分自身のソースコードと実行ファイルを作成するプログラム /// </summary> class Program {"; const string Code2 = @" static void Main(string[] args) { string thisExeName = Path.GetFileNameWithoutExtension(typeof(Program).Assembly.Location); GenerateNext(IncrementFileNumber(thisExeName)); } /// <summary> /// 文字列にするために文字列の""を""""に変換する /// </summary> public static string ConvertCode(string source) { string result = source.Replace(""\"""", ""\""\""""); return ""@\"""" + result + ""\""""; } /// <summary> /// 次のC#コードと、実行ファイルを作成する /// </summary> public static void GenerateNext(string fileName) { //ソースコードを作成する(あえてファイルコピーではなく) string csFileName = fileName + "".cs""; using (StreamWriter sw = new StreamWriter(csFileName)) { sw.WriteLine(Code1); sw.WriteLine("" const string Code1 = {0};"", ConvertCode(Code1)); sw.WriteLine("" const string Code2 = {0};"", ConvertCode(Code2)); sw.WriteLine(Code2); } Console.WriteLine(""{0}を作成しました。"", csFileName); //実行ファイルを作成する string outputName = fileName + "".exe""; CSharpCodeProvider cscp = new CSharpCodeProvider(); string[] assemblyNames = { ""System.dll"" }; CompilerParameters pars = new CompilerParameters(assemblyNames, outputName); pars.GenerateExecutable = true; CompilerResults results = cscp.CompileAssemblyFromFile(pars, csFileName); if (results.Errors.Count > 0) { foreach (var error in results.Errors) { Console.WriteLine(error); } } else { Console.WriteLine(""{0}を作成しました。"", outputName); Console.WriteLine(""実行しますか?(y/n)""); ConsoleKeyInfo keyinfo = Console.ReadKey(); if (keyinfo.Key == ConsoleKey.Y) { Process.Start(outputName); } } } /// <summary> /// ファイル番号をインクリメントする /// </summary> public static string IncrementFileNumber(string src) { Match m = Regex.Match(src, @""(?<Name>[^0-9]*)(?<Num>[0-9]*)""); string fileName = m.Groups[""Name""].Value; int number; int.TryParse(m.Groups[""Num""].Value, out number); return fileName + (number + 1); } } }"; static void Main(string[] args) { string thisExeName = Path.GetFileNameWithoutExtension(typeof(Program).Assembly.Location); GenerateNext(IncrementFileNumber(thisExeName)); } /// <summary> /// 文字列にするために文字列の"を""に変換する /// </summary> public static string ConvertCode(string source) { string result = source.Replace("\"", "\"\""); return "@\"" + result + "\""; } /// <summary> /// 次のC#コードと、実行ファイルを作成する /// </summary> public static void GenerateNext(string fileName) { //ソースコードを作成する(あえてファイルコピーではなく) string csFileName = fileName + ".cs"; using (StreamWriter sw = new StreamWriter(csFileName)) { sw.WriteLine(Code1); sw.WriteLine(" const string Code1 = {0};", ConvertCode(Code1)); sw.WriteLine(" const string Code2 = {0};", ConvertCode(Code2)); sw.WriteLine(Code2); } Console.WriteLine("{0}を作成しました。", csFileName); //実行ファイルを作成する string outputName = fileName + ".exe"; CSharpCodeProvider cscp = new CSharpCodeProvider(); string[] assemblyNames = { "System.dll" }; CompilerParameters pars = new CompilerParameters(assemblyNames, outputName); pars.GenerateExecutable = true; CompilerResults results = cscp.CompileAssemblyFromFile(pars, csFileName); if (results.Errors.Count > 0) { foreach (var error in results.Errors) { Console.WriteLine(error); } } else { Console.WriteLine("{0}を作成しました。", outputName); Console.WriteLine("実行しますか?(y/n)"); ConsoleKeyInfo keyinfo = Console.ReadKey(); if (keyinfo.Key == ConsoleKey.Y) { Process.Start(outputName); } } } /// <summary> /// ファイル番号をインクリメントする /// </summary> public static string IncrementFileNumber(string src) { Match m = Regex.Match(src, @"(?<Name>[^0-9]*)(?<Num>[0-9]*)"); string fileName = m.Groups["Name"].Value; int number; int.TryParse(m.Groups["Num"].Value, out number); return fileName + (number + 1); } } }
[Y]キーを押し続けると、延々と同じソースを持つファイルを生成し続けます。
なにか有用な応用はないものでしょうか?
内容が全く同じコードなのでこれでは進化しません。実際の生命は完全なコピーではなく、ランダムにごく一部が間違って次世代にコピーされ、環境にたまたま適応できた個体が生き残り次世代を生成できます。いわゆる淘汰です。これを実装する方法はまだ考えていませんので、今回のエントリはこれにて終わりです。大胆なタイトルをつけてしまいましたが、C#生命体の実現への道のりはまだまだ遠そうです。。
実は、以前の自分のわんくまブログエントリ「自分自身を出力するプログラム。」が元ネタです。元ネタではJavaScriptコードで記述しています。
ここで宣伝するのも恐縮なのですが2011年12月3日に「C# ポケットリファレンス 」WINGSプロジェクト著、技術評論社 が発売されました。 私は第7章を担当しました。
ご興味がある方は、ぜひオフィス・チームに一冊購入してみてください。もちろん一冊以上でも! |
宣伝といえば、C# Advent Calendar 2011の21日目ご担当のsuerさんのエントリ「Visual Studio で作成したセットアッププロジェクトで CI するためのコマンド」でご紹介いただいていたのでこれも…昨年の7月に発売された本ですが・・・ 「はじめてのVisual Studio 2010 (TECHNICAL MASTER 62)」techbank.jp著、秀和システム 私は第11章を担当しました。 この章、内容が内容だけにあまり読者いないだろうななどと思っていたのですが、21日目のsuerさんにお読みいただいていることがわかりうれしいかぎりです。suerさんありがとうございます。 |