динамические и необязательные параметры метода

Таким образом, C# поддерживает необязательные параметры:

void MethodName(string param = "optional!") { }

И параметры динамического метода:

void MethodName(dynamic param) { }

Но, к сожалению, вы не можете использовать их вместе (необязательные значения параметров должны быть константами):

void MethodName(dynamic param = new { p1 = "", p2 = 0M }) { }

Я использовал такие вещи в прошлом:

void GenericAnonHandler<T>(IEnumerable<T> param)
{
    foreach(T item in param)
    {
        var typedItem = Cast(item, new { p1 = "", p2 = 0M });
        var p2 = typedItem.p2; // Hark, IntelliSense!
    }
}    
static T Cast<T>(object obj, T type)
{
    return (T)obj;
}
void CallingMethod()
{
    var list1 = new List<ThisType>() { ... };
    var list2 = new List<ThatType>() { ... };

    var anon1 = list1
        .Select(x => 
            new { p1 = x.sPropName, p2 = x.dPropName });

    var anon2 = list2
        .Select(x => 
            new { p1 = x.sPropName2, p2 = x.dPropName2 });

     var both = anon1.Concat(anon2);

     GenericAnonHandler(both);
}

но это много дополнительной работы и кодирования для конкретного типа, где новый класс или просто использование динамического было бы проще, если вы ЗНАЕТЕ, каким должен быть динамический тип ... но динамика не дает IntelliSense (и разумно так).

Я бы предпочел использовать интерфейс, но не могу, потому что исходные типы (в этом примере: ThisType, ThatType) имеют разные имена свойств, и я не могу их контролировать (сторонняя сборка).

Однако они ЯВЛЯЮТСЯ частичными классами, поэтому я мог бы создать интерфейс с сигнатурой анонимного типа с унифицированными именами свойств И разными именами свойств, реализовать интерфейс в частичном, создать фиктивные свойства для отсутствующих значений из "другого" введите, а затем извлеките значения из соответствующих свойств в зависимости от типа.

... но это также слишком много работы ... особенно, если я приложу все усилия, чтобы создать 3 новых элемента (интерфейс, 2 частичных). Было бы проще создать тип анона как настоящий класс и перевести его из 2-х предыдущих типов.

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

Я знаю, что это глупый вопрос.. и в основном сводится к следующему: как я могу определить класс, не определяя его на самом деле.. просто интересно, есть ли какие-нибудь волшебники с трюками в рукавах, кроме маршрута Cast(T,Type) :)


person JoeBrockhaus    schedule 21.02.2013    source источник
comment
Разве вы не можете просто использовать обычный, неанонимный тип?   -  person svick    schedule 22.02.2013
comment
Это может помочь. en-us/library/dd268536(v=vs.100).aspx   -  person user959729    schedule 22.02.2013


Ответы (2)


Ответ здесь: не делайте этого.

Достаточно краткого определения: struct StrDec { public string p1; public decimal p2; }

Не стесняйтесь использовать множество этих небольших определений; предпочтительно с именами, которые фактически документируют то, что они представляют. Вы получите проверку времени компиляции, IntelliSense, и ваш код будет более читабельным, поскольку это форма документации. Вы также можете вместо этого использовать Tuple, хотя я считаю, что они делают ваш код менее читаемым, особенно если им нужно перейти в сильно вложенную спецификацию универсального аргумента. Тем не менее, кортежи все же лучше, чем хак типа динамика+анонимный.

Хорошей отправной точкой будет...

  • Никогда не используйте dynamic: это крайняя мера.
  • Никогда не используйте необязательные аргументы: это тоже крайняя мера.

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

Необязательные аргументы плохи, потому что они нарушают инкапсуляцию, которая является структурным программированием 101 с 1970-х годов — обертывание необязательных аргументов подразумевает повторение спецификаций аргументов и повторяющихся значений по умолчанию. Кроме того, существует незначительное техническое ограничение, касающееся того факта, что они разрешаются вызывающей стороной, а не вызываемой, что вас не укусит, если вы не развернете dll довольно необычным образом (это ограничение имеет значение для больших библиотек, таких как сама .NET). ). Вместо этого рассмотрите (еще раз) маленькие одноразовые structs.

person Eamon Nerbonne    schedule 21.02.2013
comment
Я не понимаю: как необязательные аргументы нарушают инкапсуляцию? - person svick; 22.02.2013
comment
@svick: Как написать оболочку для int Frobber(int x=1, string p="q", double y=3.5);? Или, другими словами, каково объявление функции, которая принимает те же параметры, но возвращает 2 * Frobber(...)? Чтобы было ясно, я вижу много объявлений методов, которые значительно длиннее этого.... - person Eamon Nerbonne; 22.02.2013
comment
(Я не хочу сказать, что каждое использование необязательных аргументов проблематично, но это дурная привычка, и она вызывает проблемы с обслуживанием. Это также запах кода, поскольку это самый простой способ написать функцию, которая нарушает SRP, что снова , является проблемой обслуживания.) - person Eamon Nerbonne; 22.02.2013
comment
Я согласен, что мне не нравятся динамические и необязательные параметры. Я забыл, что задал этот вопрос, и вернулся, думая, ничего себе, я действительно потратил все это время, пытаясь избежать написания 2 методов для использования 2 объектов, которые должны быть одинаковыми, но не являются? Одним словом, да. Наверное, мне было очень скучно, лол.. - person JoeBrockhaus; 16.05.2013

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

public class ThirdType
{
    public string P1 { get; set; }
    public decimal P2 { get; set ; }

    // you may want to add constructors
}

Затем создайте несколько методов сопоставления (методы расширения подойдут)

public static class MapperExtensions 
{
    public static ThirdType ToThirdType(this ThisType obj)
    {
        return new ThirdType() { P1 = obj.sPropName, P2 = obj.dPropName };
    }

    public static ThirdType ToThirdType(this ThatType obj)
    {
        return new ThirdType() { P1 = obj.sPropName2, P2 = obj.dPropName2 };
    }
}

Теперь всякий раз, когда вы вызываете свой метод,

void MethodName(ThirdType param)
{
    // A.) do this...
    if (param == null)
        param = new ThirdType() { P1 = "", P2 = 0M };

}

// ... or B.) create an overload (preferable to me)
void MethodName()
{
   MethodName(new ThirdType() { P1 = "", P2 = 0M });
}

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

private void ExecuteForBoth(ThisType obj1, ThatType obj2) // dummy method, just for illustration
{
    MethodName(obj1.ToThirdType());
    MethodName(obj2.ToThirdType());
}

Я написал это без intellisense, извините за опечатки.

person Mike Fuchs    schedule 21.02.2013
comment
Это похоже на то, что я всегда делал, и в основном пытался избежать необходимости писать. Особенно, если таких типов было много. AutoMapper был бы разумно инкапсулированным решением. - person JoeBrockhaus; 14.08.2015