Слишком много методов очень похожи - PullRequest
2 голосов
/ 23 апреля 2020

У меня есть много методов, которые очень похожи, как показано в коде ниже:

    public static void ReadFromKeyboard(string label, out int retVal)
    {
        try
        {
            Console.Write(label);
            retVal = int.Parse(Console.ReadLine());
        }
        catch (Exception)
        {
            Console.WriteLine("Please insert int value.");
            ReadFromKeyboard(label, out retVal);
        }
    }

    public static void ReadFromKeyboard(string label, out float retVal)
    {
        try
        {
            Console.Write(label);
            retVal = float.Parse(Console.ReadLine());
        }
        catch (Exception)
        {
            Console.WriteLine("Please insert float value.");
            ReadFromKeyboard(label, out retVal);
        }
    }

    public static void ReadFromKeyboard(string label, out double retVal)
    {
        try
        {
            Console.Write(label);
            retVal = double.Parse(Console.ReadLine());
        }
        catch (Exception)
        {
            Console.WriteLine("Please insert double value.");
            ReadFromKeyboard(label, out retVal);
        }
    }

С другой стороны, я не знаю, какой метод я буду вызывать. Я игнорирую это только во время выполнения.

Есть ли способ, которым я мог бы переписать эти многие методы в один метод с именем что-то вроде "ReadFromKeyboard", который возвращает либо int, float, либо double, в зависимости от типа который передается ему в качестве параметра?

Спасибо!

Ответы [ 4 ]

6 голосов
/ 23 апреля 2020

Как показали другие ответы, вы можете устранить дублированный код различными способами, которые ужасны, и вам не следует их делать.

В частности, не пытайтесь использовать дженерики для решения этой «проблемы» . Обобщение предназначено для ситуаций, когда код является обобщенным c. Вот почему они называются дженерики ! То есть код действует одинаково для всех возможных типов . Ваш пример противоположен универсальному c коду; у вас есть различные правила для небольшого числа типов , и способ справиться с этой ситуацией состоит в том, чтобы сделать именно то, что вы уже сделали: реализовать один метод для другого правила .

Я говорю «проблема» в кавычках, потому что на самом деле у вас нет проблемы для ее решения, поэтому прекратите пытаться ее решить. Написание полдюжины подобных коротких методов не является серьезным бременем для авторов или сопровождающих.

Теперь, несмотря на это, ваш код также не так хорош, как мог бы, и вы должны переписать его. Правильный способ написания вашего кода:

public static int ReadInteger(string label)
{
    while(true)
    {
        int value;
        Console.Write(label);
        string read = Console.ReadLine();
        bool success = int.TryParse(read, out value);
        if (success)
          return value;
        Console.WriteLine("Please type an integer value.");
    }
}

Проблемы с исходной реализацией:

  • Не используйте обработку исключений в качестве основного потока управления. Не ловите исключение, если исключение можно избежать. Вот для чего предназначен TryParse.
  • Не используйте рекурсию в качестве неограниченного цикла. Если вам нужен неограниченный l oop, для этого и нужен while(true). Помните, что C# не является хвостовой рекурсивной по умолчанию!
  • Не используйте параметры без необходимости. Метод логически возвращает целое число, поэтому на самом деле возвращает целое число . Переименуйте его, чтобы не было конфликтов с другими методами чтения. Нет убедительных преимуществ в том, чтобы вызывающий абонент писал Read<int> вместо ReadInteger, и много неотложных преимуществ в том, чтобы избегать выходного параметра.
0 голосов
/ 23 апреля 2020

Если вы хотите полагаться на разрешение перегрузки для среды выполнения, чтобы решить, какой метод вызывать, то у вас должен быть отдельный метод для каждого типа, который вы будете поддерживать. Вот как это работает.

С другой стороны, если вы можете позволить пользователю предоставлять хотя бы небольшую информацию о типах, мы можем немного улучшить ситуацию с помощью шаблонов, удалив try/catch и использовав реальный * 1004. * заявление. Вы бы назвали это так:

var myNumber = ReadFromKeyboard<double>("Enter a double: ");

И код будет выглядеть так:

public static T ReadFromKeyboard<T>(string label, int maxRetries = int.MaxValue)
{
    while (maxRetries >= 0)
    {
        Console.Write(label);

        if (typeof(T) == typeof(int))
        {
            int result;
            if (int.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
        }
        if (typeof(T) == typeof(float))
        {
            float result;
            if (float.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
        }
        else if (typeof(T) == typeof(double))
        {
            double result;
            if (double.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
        }
        else if (typeof(T) == typeof(decimal))
        {
            decimal result;
            if (decimal.TryParse(Console.ReadLine(), out result)) return (T)(object)result;
        }
        else 
            throw new InvalidOperationException("Unsupported type");
        maxRetries--;
    }
    throw new InvalidOperationException("Too many bad inputs");
}

Но для того, чтобы он заработал, вам нужно выполнить несколько очень дурацких приведений и проверок типов , Существует вероятность того, что это может вызвать исключение, которое, по-видимому, вы хотите избежать, но если ваш пользователь сидит там более 2 миллиардов попыток, я сомневаюсь, что они будут очень удивлены.

0 голосов
/ 23 апреля 2020

Вместо перечисления всех возможных типов (которые вы, возможно, не знаете заранее), можно использовать класс System.Convert, особенно метод Convert.ChangeType(). В качестве подтверждения концепции вы можете использовать такой метод:

public static void ReadFromKeyboard<T>(string label, out T result) {
    Type targetType = typeof(T);
    Console.Write($"{label}: ");
    string input = Console.ReadLine();
    object convertedValue = Convert.ChangeType(input, targetType);
    result = (T)convertedValue;
}

Этот метод можно использовать следующим образом:

public static void Main(string[] args) {
    ReadFromKeyboard("enter a double", out double d);
    ReadFromKeyboard("enter an int", out int i);
    Console.WriteLine($"double: {d}");
    Console.WriteLine($"int: {i}");
}

Таким образом, можно использовать любой тип, который вы хотите (при условии, что он поддерживается классом Convert). Очевидно, что вы можете добавить обработку исключений и do-while l oop в методе ReadFromKeyboard, если хотите.

0 голосов
/ 23 апреля 2020

Я пытался реализовать код по Eri c Lippert рецептам. Код ниже

  1. не использует обработку исключений, поскольку основной поток управления
  2. вообще не использует рекурсию
  3. не использует выходные параметры без необходимости

.

private static void Main(string[] args)
{
    int intValue = ReadFromKeyboardInt32("enter int");
    float floatValue = ReadFromKeyboardSingle("enter float");
    double doubleValue = ReadFromKeyboardDouble("enter double");

    Console.WriteLine($"{intValue}, {floatValue}, {doubleValue}");
}

public static Double ReadFromKeyboardDouble(string label) =>
    ReadFromKeyboard(label, (text) => (Double.TryParse(text, out var value), value));

public static Int32 ReadFromKeyboardInt32(string label) =>
    ReadFromKeyboard(label, (text) => (Int32.TryParse(text, out var value), value));

public static Single ReadFromKeyboardSingle(string label) =>
    ReadFromKeyboard(label, (text) => (Single.TryParse(text, out var value), value));

public static T ReadFromKeyboard<T>(string label, Func<string, (bool, T)> tryParse)
{
    for (; ; )
    {
        Console.Write($"{label}: ");
        var result = tryParse(Console.ReadLine());
        if (result.Item1)
        {
            return result.Item2;
        }
        Console.WriteLine($"Please enter valid {typeof(T).Name} value");
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...