見出し画像

C#初心者を卒業しよう(第7回の解答例)


プログラム

 Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services
    .AddTransient<DistanceService>()
    .AddScoped<InputConvertNumber>()
    .AddScoped<IInputConvertNumber, InputConvertNumber>()
    .AddScoped<InputDistance>()
    .AddScoped<IInputDistance, InputDistance>()
    .AddScoped<InputOutputNumber>()
    .AddScoped<IInputOutputNumber, InputOutputNumber>()
    .AddScoped<OutputConsole>()
    .AddScoped<IOutputAnswer, OutputConsole>()
    .AddScoped<OutputFile>()
    .AddScoped<IOutputAnswer, OutputFile>()
    .AddScoped<ToYards>()
    .AddScoped<IDistance, ToYards>()
    .AddScoped<ToMeters>()
    .AddScoped<IDistance, ToMeters>();
using IHost host = builder.Build();

var service = host.Services.GetService<DistanceService>() ??
    throw new NotImplementedException(
        "Faild to create 'DistanceService' object.");
service.Run();

public enum ConvertNumber
{
    ToYards = 1,
    ToMeters = 2,
}

public enum OutputNumber
{
    Console = 1,
    File = 2,
}

public class ConvertValues
{
    public Type ConvertType { get; }
    public string ConvertMessage { get; }

    public ConvertValues(Type convertType, string convertMessage)
    {
        ConvertType = convertType;
        ConvertMessage = convertMessage;
    }
}

public class Const
{
    public static readonly double METER_TO_YARD = 1.0936133d;

    public static readonly IReadOnlyDictionary<ConvertNumber, ConvertValues> CONVERT_VALUE_DIC =
        new Dictionary<ConvertNumber, ConvertValues>
    {
        {
            ConvertNumber.ToYards,
            new ConvertValues(
                typeof(ToYards),
                $"{(int)ConvertNumber.ToYards} : Convert meters to yards.")
        },
        {
            ConvertNumber.ToMeters,
            new ConvertValues(
                typeof(ToMeters),
                $"{(int)ConvertNumber.ToMeters} : Convert yards to meters.")
        },
    };

    public static readonly IReadOnlyDictionary<OutputNumber, Type> OUTPUT_DIC =
        new Dictionary<OutputNumber, Type>
    {
        {OutputNumber.Console, typeof(OutputConsole)},
        {OutputNumber.File, typeof(OutputFile)},
    };
}

public interface IDistance
{
    void Calculate(double distance);
    string CreateAnswerMessage();
}

public class DistanceService(IServiceProvider serviceProvider)
{
    public void Run()
    {
        var convertNumber = InputConvertNumber();
        var distanceObject =
            Create(Const.CONVERT_VALUE_DIC[convertNumber].ConvertType) ??
                throw new NotImplementedException(
                    "Faild to create 'IDistance' object.");

        var distance = InputDistance();
        distanceObject.Calculate(distance);

        var output = InputOutputNumber();
        OutputAnswer(distanceObject, output);
    }

    protected virtual ConvertNumber InputConvertNumber()
    {
        var inputConvertNumber =
            (IInputConvertNumber?)serviceProvider.GetService(typeof(InputConvertNumber)) ??
                throw new NotImplementedException(
                    "Faild to create 'InputConvertNumber' object.");
        if (Enum.TryParse(
                typeof(ConvertNumber),
                inputConvertNumber.ReadConvertNumber(),
                out var convertNumber))
        {
            if (Enum.IsDefined(typeof(ConvertNumber), convertNumber))
            {
                return (ConvertNumber)convertNumber;
            }
            throw new IndexOutOfRangeException(
                $"Input is out of range. {(int)convertNumber}");
        }
        throw new FormatException("Invalid input convert number.");
    }

    protected virtual IDistance? Create(Type convertType) =>
        (IDistance?)serviceProvider.GetService(convertType);

    protected virtual double InputDistance()
    {
        var inputDistance =
            (IInputDistance?)serviceProvider.GetService(typeof(InputDistance)) ??
                throw new NotImplementedException(
                    "Faild to create 'InputDistance' object.");
        if (double.TryParse(inputDistance.ReadDistance(), out var distance))
        {
            return distance;
        }
        throw new FormatException("Invalid input distance.");
    }

    protected virtual OutputNumber InputOutputNumber()
    {
        var inputOutputNumber =
            (IInputOutputNumber?)serviceProvider.GetService(typeof(InputOutputNumber)) ??
                throw new NullReferenceException(
                    "Failed to create 'InputOutputNumber' object.");
        if (Enum.TryParse(
            typeof(OutputNumber),
            inputOutputNumber?.ReadOutputNumber(),
            out var outputNumber))
        {
            if (Enum.IsDefined(typeof(OutputNumber), outputNumber))
            {
                return (OutputNumber)outputNumber;
            }
            throw new IndexOutOfRangeException(
                $"Input is out of range. {(int)outputNumber}");
        }
        throw new FormatException("Invalid input output destination number.");
    }

    protected virtual void OutputAnswer(
        IDistance distance, OutputNumber outputNumber)
    {
        var outputMessage = distance.CreateAnswerMessage();
        var outputAnswer =
            (IOutputAnswer?)serviceProvider.GetService(Const.OUTPUT_DIC[outputNumber]) ??
                throw new NotImplementedException(
                    "Faild to create 'OutputAnswer' object.");
        outputAnswer.WriteAnswer(outputMessage);
    }
}

public interface IInputConvertNumber
{
    string? ReadConvertNumber();
}

public class InputConvertNumber : IInputConvertNumber
{
    public string? ReadConvertNumber()
    {
        Console.WriteLine("Please input convert number.");
        foreach (var value in Const.CONVERT_VALUE_DIC.Values)
        {
            Console.WriteLine(value.ConvertMessage);
        }
        return Console.ReadLine();
    }
}

public interface IInputDistance
{
    string? ReadDistance();
}

public class InputDistance : IInputDistance
{
    public string? ReadDistance()
    {
        Console.WriteLine("Please input distance.");
        return Console.ReadLine();
    }
}

public interface IInputOutputNumber
{
    string? ReadOutputNumber();
}

public class InputOutputNumber : IInputOutputNumber
{
    public string? ReadOutputNumber()
    {
        Console.WriteLine("Please enter the output destination number.");
        Console.WriteLine($"{(int)OutputNumber.Console} : Output to console.");
        Console.WriteLine($"{(int)OutputNumber.File} : Output to file.");
        return Console.ReadLine();
    }
}

public interface IOutputAnswer
{
    void WriteAnswer(string message);
}

public class OutputConsole : IOutputAnswer
{
    public void WriteAnswer(string message)
    {
        Console.WriteLine(message);
    }
}

public class OutputFile : IOutputAnswer
{
    public void WriteAnswer(string? message)
    {
        using StreamWriter sw =
            new("./output.txt", false, System.Text.Encoding.UTF8);
        sw.WriteLine(message);
    }
}

public class ToYards : IDistance
{
    private double _yards;
    private double _meters;

    public void Calculate(double meters)
    {
        _meters = meters;
        _yards = meters * Const.METER_TO_YARD;
    }

    public string CreateAnswerMessage()
    {
        return $"{_meters} meters is {_yards} yards.";
    }
}

public class ToMeters : IDistance
{
    private double _yards;
    private double _meters;

    public void Calculate(double yards)
    {
        _yards = yards;
        _meters = yards / Const.METER_TO_YARD;
    }

    public string CreateAnswerMessage()
    {
        return $"{_yards} yards is {_meters} meters.";
    }
}

ダウンロード

解説

ファイル出力クラスを作成する

public class OutputFile : IOutputAnswer
{
    public void WriteAnswer(string? message)
    {
        using StreamWriter sw =
            new("./output.txt", false, System.Text.Encoding.UTF8);
        sw.WriteLine(message);
    }
}

出力種別の列挙を定義する

public enum OutputNumber
{
    Console = 1,
    File = 2,
}

出力先の入力を促すメッセージを出力し、入力を受け付けるクラスを作成する

public interface IInputOutputNumber
{
    string? ReadOutputNumber();
}

public class InputOutputNumber : IInputOutputNumber
{
    public string? ReadOutputNumber()
    {
        Console.WriteLine("Please enter the output destination number.");
        Console.WriteLine($"{(int)OutputNumber.Console} : Output to console.");
        Console.WriteLine($"{(int)OutputNumber.File} : Output to file.");
        return Console.ReadLine();
    }
}

定数クラスに出力先を切り替える Dictionary を追加する

public static readonly IReadOnlyDictionary<OutputNumber, Type> OUTPUT_DIC =
    new Dictionary<OutputNumber, Type>
{
    {OutputNumber.Console, typeof(OutputConsole)},
    {OutputNumber.File, typeof(OutputFile)},
};

出力先を入力するメソッドを追加する

    protected virtual OutputNumber InputOutputNumber()
    {
        var inputOutputNumber =
            (IInputOutputNumber?)serviceProvider.GetService(typeof(InputOutputNumber)) ??
                throw new NullReferenceException(
                    "Failed to create 'InputOutputNumber' object.");
        if (Enum.TryParse(
            typeof(OutputNumber),
            inputOutputNumber?.ReadOutputNumber(),
            out var outputNumber))
        {
            if (Enum.IsDefined(typeof(OutputNumber), outputNumber))
            {
                return (OutputNumber)outputNumber;
            }
            throw new IndexOutOfRangeException(
                $"Input is out of range. {(int)outputNumber}");
        }
        throw new FormatException("Invalid input output destination number.");
    }

解答を出力するメソッドを変更して出力先を切り替えられるようにする

    protected virtual void OutputAnswer(
        IDistance distance, OutputNumber outputNumber)
    {
        var outputMessage = distance.CreateAnswerMessage();
        var outputAnswer =
            (IOutputAnswer?)serviceProvider.GetService(Const.OUTPUT_DIC[outputNumber]) ??
                throw new NotImplementedException(
                    "Faild to create 'OutputAnswer' object.");
        outputAnswer.WriteAnswer(outputMessage);
    }

呼び出し元に組み込む

    public void Run()
    {
        var convertNumber = InputConvertNumber();
        var distanceObject =
            Create(Const.CONVERT_VALUE_DIC[convertNumber].ConvertType) ??
                throw new NotImplementedException(
                    "Faild to create 'IDistance' object.");

        var distance = InputDistance();
        distanceObject.Calculate(distance);

        var output = InputOutputNumber();
        OutputAnswer(distanceObject, output);
    }

追加したクラス、インターフェースを DI コンテナに登録する

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services
    .AddTransient<DistanceService>()
    .AddScoped<InputConvertNumber>()
    .AddScoped<IInputConvertNumber, InputConvertNumber>()
    .AddScoped<InputDistance>()
    .AddScoped<IInputDistance, InputDistance>()
    .AddScoped<InputOutputNumber>()
    .AddScoped<IInputOutputNumber, InputOutputNumber>()
    .AddScoped<OutputConsole>()
    .AddScoped<IOutputAnswer, OutputConsole>()
    .AddScoped<OutputFile>()
    .AddScoped<IOutputAnswer, OutputFile>()
    .AddScoped<ToYards>()
    .AddScoped<IDistance, ToYards>()
    .AddScoped<ToMeters>()
    .AddScoped<IDistance, ToMeters>();




よろしければサポートをお願いします。 いただいたサポートは、書籍代、開発機器購入などに充てさせていただきます。