2015-05-07 5 views
6

Вот демонстрация проблемы:компиляции время проверки вызова метода для нескольких параметров одного и того же типа

class Program 
{ 
    static double Func(double a, double b) { return a * 1000 + b * b; } 

    static void Main(string[] args) 
    { 
     var a = 1.1d; 
     var b = 2.2d; 
     Console.WriteLine(Func(a, b)); 
     // this is the problem, function doesn't recognize when a and b 
     // "accidentally" exchanged, target is to make this row a compile-time error 
     Console.WriteLine(Func(b, a)); 
    } 
} 

Это становится проблемой, если есть методы с большим количеством параметров (например, десять из double типа):

double Func(double parameter1, double parameter2, ..., double parameter10); 

Вопрос: есть ли способ проверить параметры при вызове метода, чтобы программист менее подвержен ошибкам?

Это не проблема, если типы параметров различны. Я думал, что, может быть, оберточной в новые типы поможет:

class A 
{ 
    private double _value; 
    public static implicit operator A(double value) { return new A() { _value = value }; } 
    public static implicit operator double(A value) { return value._value; } 
} 
class B 
{ 
    private double _value; 
    public static implicit operator B(double value) { return new B() { _value = value }; } 
    public static implicit operator double(B value) { return value._value; } 
} 

class Program 
{ 
    static double Func(A a, B b) { return a * 1000 + b * b; } 

    static void Main(string[] args) 
    { 
     A a = 1.1d; 
     B b = 2.2d; 
     Console.WriteLine(Func(a, b)); 
     Console.WriteLine(Func(b, a)); // compile-time error! yay! 
     Console.WriteLine(Func(a, b) + 123.123d - a * 2); // implicit conversion power 
     Console.ReadKey(); 
    } 
} 

И это делает, но я вполне уверен, если этот метод является эффективным. У меня есть сомнения, если это хорошая идея. Это? Или там лучше?

Я знаю, что я могу быть абсолютно безопасным, если я всегда называю метод, как это (с помощью named arguments вызова метода)

Func(a:a, b:b); 

Это не должно привести любые накладные расходы в коде, но много печатать. Обертка лучше, потому что она выполняется один раз (создание нового типа легко), но, вероятно, накладные расходы.

+2

Если у вас есть десять параметров, вы можете обернуть каждую группу связанных параметров в одном типе, вы считаете, что? –

+1

Возможно, единственный способ гарантировать, что вы не будете давать неправильный аргумент, - это иметь разные типы для ** каждого ** параметра и ** никогда не использовать их в их развернутой форме. Это будет в основном закрывать один из возможных способов совершения ошибок. Является ли это реалистичным подходом или нет, и может ли он действительно улучшить соотношение дефектов на каждую линию и ремонтопригодность для всей программы [другой вопрос] (http://programmers.stackexchange.com/questions/281827/should-we-define -типы-для-всего). –

+0

@MehrzadChehraz, хорошая идея. Звучит как ответ на меня, я просто должен подумать об этом, дай мне немного времени. – Sinatr

ответ

4

Если два аргумента одного типа, невозможно определить во время компиляции, времени выполнения или иначе, что имя переменной аргумента соответствует имени параметра. Это своего рода открытый вопрос, но я предложу вам пару идей.

  • Как предложил Мехрзад, рассмотрим группировку параметров по типу. Например, вместо double Distance(double x1, double y1, double x2, double y2), рассмотрите double Distance(Point p1, Point p2)

  • В общем, если ваш метод имеет более 4-5 параметров, рассмотрите рефакторинг. Может быть, ваш метод пытается сделать слишком много вещей, и логика может быть разделена?

  • Если вы действительно хотите сделать, это выполнить некоторые проверки, такие как обеспечение того, чтобы a < b, рассмотреть вопрос о том, как смотреть в Code contracts. Вы также можете использовать Debug.Assert(), но это работает только во время выполнения.

  • Я бы не рекомендовал предлагать неявное преобразование. Для меня он чувствует себя взломанным и неинтуитивным, что A a = 1.1 не должен иметь семантической цели, кроме параметров проверки времени компиляции. Ваша конечная цель - сделать код более удобным в обслуживании.

+0

Группировка - хорошая идея, но на самом деле она не применима в моем реальном случае. Рефакторинг действительно невозможен, так как этот метод нужно вызывать во многих местах, некоторые параметры могут быть свойствами какого-либо объекта или должны быть указаны. На самом деле я всегда могу потребовать создания объекта для метода (в соответствии с ответом @AydinAdn) или, скорее, сделать нестатический метод (поэтому перед вызовом метода мне нужно создать экземпляр и во время создания свойств заливки, которые затем используются в методе) , И в отношении последней заметки рассмотрим использование 'Func (a, 123);', где 'B' прозрачно' double'. Но да, я тоже чувствую себя взломанным. – Sinatr

+0

Что делает ваш метод, точно? Нехорошо иметь методы со многими именованными параметрами, но иногда это неизбежно. Не могли бы вы хотя бы опубликовать подпись своего метода? – MariusUt

+0

Методы используются для расчета статистического значения с использованием заданного массива данных и многих других параметров, позволяют называть их конфигурациями, некоторые из них относятся к статической конфигурации, некоторые - к динамической динамике данных, а некоторые сами корректируют результаты метода (не относится к любой из конфигураций). Я хочу иметь формулу в одном месте (очевидно), и я не хочу передавать полные конфигурации в метод (связь с динамической конфигурацией не изящна), поэтому я предпочел наименования безлимитных «двойных» параметров. – Sinatr

2

У вас никогда не должно быть 10 параметров для метода.

Как только у вас есть около 4 параметров, начните рассматривать использование нового класса, чтобы содержать эти параметры ... В качестве примера рассмотрим предпочтения пользователя, перемещающегося на веб-сайте ...

void Main() 
{ 
    UserPreferences preference = new UserPreferences 
    { 
     BackgroundColor = "#fff", 
     ForegroundColor = "#000", 
     Language = "en-GB", 
     UtcOffSetTimeZone = 0 
    }; 

    User aydin = new User(preference); 
} 

public class User 
{ 
    public User(UserPreferences preferences) 
    { 
     this.Preferences = preferences; 
    } 

    public UserPreferences Preferences { get; set; } 
} 

public class UserPreferences 
{ 
    public string BackgroundColor { get; set; } 
    public string ForegroundColor { get; set; } 
    public int UtcOffSetTimeZone { get; set; } 
    public string Language { get; set; } 
} 
+0

Создание выделенного типа для хранения аргументов для метода - хорошая идея. Если, однако, таких случаев уже много, это уже не так. В данном примере 'UserPreferences' является свойством' User'. Нет необходимости создавать тип 'UserPreference' специально для построения объекта, они могут быть свойствами' User' и заполняться после построения, если вы не используете тип 'UserPreferences' в другом месте (например, для копирования предпочтений от одного пользователя к другому, быть более элегантным по сравнению со значениями значений копирования). – Sinatr

+0

Да, вам не нужен объект 'UserPreference' для создания' User' в реальном мире, но он намного более изящный, чем конструктор с 4 параметрами, я просто хотел поделиться своей концепцией –

0

Используйте унаследованный класс что-то вроде этого

class Program 
    { 
     static double Func(List<Parent> l) { return l[0]._value * 1000 + l[1]._value * l[1]._value; } 

     static void Main(string[] args) 
     { 
      A a = 1.1d; 
      B b = 2.2d; 
      Console.WriteLine(Func(new List<Parent>() {a,b})); 
      Console.WriteLine(Func(new List<Parent>() { a, b })); // compile-time error! yay! 
      Console.WriteLine(Func(new List<Parent>() { a, b }) + 123.123d - a * 2); // implicit conversion power 
      Console.ReadKey(); 
     } 
    } 
    class Parent 
    { 
     public double _value { get; set; } 
    } 
    class A : Parent 
    { 
     public static implicit operator A(double value) { return new A() { _value = value }; } 
     public static implicit operator double(A value) { return value._value; } 
    } 
    class B : Parent 
    { 
     public static implicit operator B(double value) { return new B() { _value = value }; } 
     public static implicit operator double(B value) { return value._value; } 
    } 
+0

Я превратил код фрагмент кода в блок кода - Исходные фрагменты кода имеют смысл только для вопросов с HTML, CSS или JavaScript - http://meta.stackoverflow.com/questions/276409/replace-code-snippets-by-normal-code-block-if -javascript-таг-это-отсутствует. –

Смежные вопросы