課題の進め方

基本的には適当なプロジェクト/ソリューションを作って課題を進めた上で,Program.csのみ(特に他に指示がなければ)を提出する.

他の人(受講者・非受講者両方)に解答内容(一部でも)を見せない,そして他の人の解答内容(一部でも)を見ないこと.コピペ(一部でも,媒体問わず)も剽窃の一種であり,レポートにおける剽窃は試験における不正行為と同様に扱われることに留意お願いします.また,公開の場所に解答を置かないようお願いします.GitHubやBitbucket等は使える人は使えばよいと思いますが,privateレポジトリにするようにお願いします.

プロジェクト/ソリューションの作成

Note

再掲:プロジェクトは一つの実行形式やライブラリを作成するためのコード等を全てまとめたものであり,ソリューションは関連するプロジェクトをまとめたもの(参考:What are solutions and projects in Visual Studio).

適当な名前(たとえば課題3の解答なのでQ3にするなど)のフォルダを適当な場所に作成し, VSCodeで作成したフォルダを開く.そして,VSCode内のターミナルで以下を実行する.

dotnet new console -o .

課題の実施

Program.csを問題文の指示の通りに編集する(課題によっては他のファイルも.ただし提出・採点手続きの簡略化のため提出する.csファイルはProgram.csのみ).作成した.csファイルには先頭部分に学籍番号と名前をコメントとして含めること.なので,たとえば学籍番号Z0TB9999の東北 大学さんの提出ファイルは

// Z0TB9999
// 東北 大学

という行から始まる.

提出

できあがった Program.csをClassroom内の当該回の「課題」より提出する.最初のステップで作成したフォルダにあるはず.また問題文に指示がある場合はそのファイル(例:課題4のitems.txt)も提出する.提出前には以下を確認しよう.

    • ただし,友人の解答を見ない(一部でも),そして友人に解答を見せない(一部でも)ようお願いします

基本課題

以下の内容のLogger.csを考える.

// Logger.cs 
using System; 

// 以下のクラスは変更してはならない.
// ロガーを表す.ロガーの役割はログを取ること.ログは想定外の挙動が起きたときに原因を特定するのに役立つ.
//
// ログの出力先は標準エラー出力,ファイル,DBなどいろいろ考えられるが,
// ここでは簡単のため単に標準出力とする.
class Logger 
{
   public virtual void Log(string s)
   {
      // 文字列sを標準出力に改行付きで出力する.
      Console.WriteLine(s);
   }
}

このLogger.csProgram.csと同じフォルダに置くものとし,以後変更しない.提出するのはProgram.csのみでありLogger.csは提出物に含めない

このLoggerクラスを継承し,以下のコンストラクタ・メソッドを備えたロガークラスDebugLoggerを作成せよ.ただし,Loggerクラスを変更してはならない.

コンストラクタ・メソッド 説明
DebugLogger(int l) 現在のデバッグレベルがlであるようなDebugLoggerを作成するコンストラクタ.
void SetDebugLevel(int l) 現在のデバッグレベルをlに設定する.
void Log(int l, string m) 現在のデバッグレベルがl以上であれば,mをログ出力する.そうでなければなにもしない.

Note

「現在のデバッグレベル」は,よくあるコマンドラインプログラムに--verboseなどで渡すものを想像するピンと来る人もいるかもしれない.

提出するプログラムは以下のクラスProgramを持つこと.

// 変更しない
class Program 
{
   static void Main()
   {
      new App().Start(); 
   }
}

また,以下の内容のApp.csProgram.csと同じフォルダに置くことにする.App.csも提出物には含めない.

// App.cs
class App 
{
   static int Sum(int n, Logger logger) 
   {
      logger.Log("Sum is called with " + n);
      int s = 0; 
      for (int i = 1; i <= n; i++) 
      {
         s += i; 
      }
      logger.Log("Sum(" + n + ") is " + s);
      return s;
   }

   static int ComplexFunc(int n, DebugLogger logger)
   {
      logger.Log(1, "ComplexFunc is called with " + n);
      int v = n; int step = 0; 

      // 適当に複雑な計算 
      // コラッツの予想
      while (v != 1) 
      {
         if ((v & 1) == 0) 
         {
            // 偶数のときは2で割るだけなので,あまり面白くない.
            // なので,デバッグレベルが高いときのみ出力することにする.
            logger.Log(3, v.ToString());
            v = v / 2; 
         }
         else 
         {
            // こんな計算どこで間違えるんだと思われるかもしれないが,
            // nによってはvが途中でオーバフローする場合がある(n = 113383を試してみよう).
            logger.Log(2, v.ToString());
            v = 3 * v + 1 ; 
         }
         step++; 
      }
      logger.Log(1, "ComplexFunc(" + n + ") is " + step);
      return step; 
   }

   public void Start()
   {
      Sum(10, new Logger()); 

      DebugLogger dlogger = new DebugLogger(0);
      Sum(20, dlogger); 

      Console.WriteLine("----");
      ComplexFunc(3, dlogger); 

      // デバッグレベルを増やして同じメソッドを呼び出す
      Console.WriteLine("----");
      dlogger.SetDebugLevel(1); 
      ComplexFunc(3, dlogger); 

      // デバッグレベルをさらに増やして同じメソッドを呼び出す
      Console.WriteLine("----");
      dlogger.SetDebugLevel(2); 
      ComplexFunc(3, dlogger); 

      // デバッグレベルをまたさらに増やして同じメソッドを呼び出す
      Console.WriteLine("----");
      dlogger.SetDebugLevel(3); 
      ComplexFunc(3, dlogger); 
   }
}

期待される出力の例

Sum is called with 10
Sum(10) is 55
Sum is called with 20
Sum(20) is 210
----
----
ComplexFunc is called with 3
ComplexFunc(3) is 7
----
ComplexFunc is called with 3
3
5
ComplexFunc(3) is 7
----
ComplexFunc is called with 3
3
10
5
16
8
4
2
ComplexFunc(3) is 7

発展課題

Important

本課題を完了できたのならば本課題の解答のみを提出すればよく,基本課題の解答は提出する必要はない.

基本課題で作成した DebugLogger をさらに継承し,ログ出力時に特定の接頭辞を付けるようログ出力するロガークラスPrefixDebugLoggerを作成せよ.具体的には,以下のコンストラクタとメソッド

コンストラクタ・メソッド 説明
PrefixDebugLogger(int l, string prefix) 現在のデバッグレベルがlで,接頭辞がprefixであるPrefixDebugLoggerを作成する.

を持ち,また以下のようにLogメソッドの挙動を変更する.

メソッド 変更点
void Log(string m) mを現在の接頭辞付きで標準出力に改行付きで出力する.
void Log(int l, string m) 現在のデバッグレベルがl以上であれば,mを現在の接頭辞付きで標準出力に改行付きで出力する.そうでなければなにもしない.

ただし,DebugLogger.Log(int l, string m)は仮想関数にせず,またDebugLoggerの挙動を基本課題から変更してはならないとする.DebugLogger.Log(int, string)の実装を工夫すれば,このメソッドをPrefixDebugLoggerでオーバライドする必要はないはず.

App.Startにたとえば以下のような記述を追加し動作確認するとよい(既存の処理を削除してはならない).

class App
{
   public void Start()
   {
         // … 基本課題と同じ部分については省略 …

         // 出力の区切り
         Console.WriteLine("========");

         DebugLogger dlogger2 = new PrefixDebugLogger(1, "> ");

         // 以下が出力される
         // > Sum is called with 10
         // > Sum(10) is 55
         Sum(10, dlogger2); 

         // 以下が出力される
         // > ComplexFunc is called with 7
         // > ComplexFunc(7) is 16
         ComplexFunc(7, dlogger2);

         DebugLogger dlogger3 = new PrefixDebugLogger(1, "^_^;) "); 

         // 以下が出力される
         // ^_^;) Sum is called with 10
         // ^_^;) Sum(10) is 55
         Sum(10, dlogger3); 
   }
}

ヒント: オーバライドを使う?

Note

基本クラスBとその派生クラスDに引数の型・数が同じ同名のメソッドがあった場合に,そのメソッドがBの仮想メソッドでDがそれをオーバライドしているわけではない場合には,インスタンスの静的な型によって呼び出されるメソッドが決まる.

たとえば,以下のようなクラスBDがあったとする.

class B 
{
  public void f() { Console.WriteLine("B"); }
}

class D : B
{
  public void f() { Console.WriteLine("D"); }
}

このとき以下のような挙動になる.

new B().f(); // Bが表示される
new D().f(); // Dが表示される

B b = new D(); 
b.f(); // Bが表示される
((B) new D()).f(); // Bが表示される