Вернуть первый метод, который работает, более элегантным способом? - PullRequest
3 голосов
/ 23 марта 2011

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

private void InitContent()
{
    if (!String.IsNullOrEmpty(Request.QueryString["id"]))
    {
        Content = GetContent(Convert.ToInt64(Request.QueryString["id"]));
        ContentMode = ContentFrom.Query;
    }

    if (Content == null && DefaultId != null)
    {
        Content = GetContent(DefaultId);
        ContentMode = ContentFrom.Default;
    }

    if (Content == null) ContentMode = ContentFrom.None;
}

Здесь метод GetContent должен возвращать null, если id отсутствует в базе данных.Это короткий пример, но вы можете представить, как это могло бы стать неуклюжим, если бы было больше вариантов.Есть ли лучший способ сделать это?

Ответы [ 6 ]

9 голосов
/ 23 марта 2011

Нулевой оператор объединения может иметь желаемую семантику.

q = W() ?? X() ?? Y() ?? Z();

Это по сути то же самое, что и

if ((temp = W()) == null && (temp = X()) == null && (temp == Y()) == null)
    temp = Z();
q = temp;

То есть q является первым ненулевым значением W (), X (), Y () или, если все они нулевые, то Z ().

Вы можете связать столько, сколько захотите.

Точная семантика не совсем такая, как я набросал; правила преобразования типов хитры. Смотрите спецификации, если вам нужны точные детали.

3 голосов
/ 23 марта 2011

Вы также можете сделать что-то более подлое, в соответствии с этим:

private Int64? GetContentIdOrNull(string id)
{
    return string.IsNullOrEmpty(id) ? null : (Int64?)Convert.ToInt64(id);
}

private Int64? GetContentIdOrNull(DefaultIdType id)
{
    return id;
}

private void InitContent()
{
    // Attempt to get content from multiple sources in order of preference

    var contentSources = new Dictionary<ContentFrom, Func<Int64?>> {
        { ContentFrom.Query,   () => GetContentIdOrNull(Request.QueryString["id"]) },
        { ContentFrom.Default, () => GetContentIdOrNull(DefaultId) }
    };

    foreach (var source in contentSources) {
        var id = source.Value();
        if (!id.HasValue) {
            continue;
        }

        Content = GetContent(id.Value);
        ContentMode = source.Key;

        if (Content != null) {
            return;
        }
    }

    // Default
    ContentMode = ContentFrom.None;
}

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

2 голосов
/ 23 марта 2011

Лично я нахожу, что когда у меня много утверждений, которые кажутся несопоставимыми, пришло время сделать некоторые функции.

private ContentMode GetContentMode(){
}

private Content GetContent(int id){
}

private Content GetContent(HttpRequest request){
   return GetContent(Convert.ToInt64(request.QueryString["id"]));
}

private void InitContent(){
  ContentMode mode = GetContentMode();
  Content = null;
  switch(mode){
    case ContentMode.Query:
       GetContent(Request);
       break;
    case ContentMode.Default:
       GetContent(DefaultId);
       break;
    case ContentMode.None:
       ... handle none case...
       break;

  }
}

Таким образом, вы разделяете свои намерения - первый шаг, определите режим контента. Затем получите контент.

1 голос
/ 23 марта 2011

Хорошо, потому что я заметил немного поздно, что вы на самом деле хотели режим ContentFrom, я приложил все усилия, чтобы придумать перевод вашего образца ниже моего исходного ответа


В общем, я использую следующую парадигму для подобных случаев. Найдите и замените ваши конкретные методы здесь и там:)

IEnumerable<T> ValueSources()
{
     yield return _value?? _alternative;
     yield return SimpleCalculationFromCache();
     yield return ComplexCalculation();
     yield return PromptUIInputFallback("Please help by entering a value for X:");
}

T EffectiveValue { get { return ValueSources().FirstOrDefault(v => v!=null); } }

Обратите внимание, как теперь вы можете сделать v!=null произвольно «интересным» для ваших целей.

Обратите внимание также, как ленивая оценка гарантирует, что вычисления никогда не выполняются, когда _value или _alternative установлены на «интересные» значения


Вот моя первая попытка поместить ваш образец в эту форму. Обратите внимание, как я добавил довольно много сантехники, чтобы убедиться, что это на самом деле компилируется в C # exe:

using System.Collections.Generic;
using System.Linq;
using System;
using T=System.String;

namespace X { public class Y
{
    public static void Main(string[]args) 
    {
        var content = Sources().FirstOrDefault(c => c); // trick: uses operator bool()
    }

    internal protected struct Content
    {
        public T Value;
        public ContentFrom Mode;
        //
        public static implicit operator bool(Content specimen) { return specimen.Mode!=ContentFrom.None && null!=specimen.Value; }
    }

    private static IEnumerable<Content> Sources()
    {
        // mock
        var Request = new { QueryString = new [] {"id"}.ToDictionary(a => a) };

        if (!String.IsNullOrEmpty(Request.QueryString["id"]))
            yield return new Content { Value = GetContent(Convert.ToInt64(Request.QueryString["id"])), Mode = ContentFrom.Query };
        if (DefaultId != null)
            yield return new Content { Value = GetContent((long) DefaultId), Mode = ContentFrom.Default };
        yield return new Content();
    }

    public enum ContentFrom { None, Query, Default };
    internal static T GetContent(long id) { return "dummy"; }
    internal static readonly long? DefaultId = 42;

} }
1 голос
/ 23 марта 2011

Я предлагаю вам попробовать какой-нибудь шаблон дизайна фабрики для этого случая.Вы можете абстрагировать процедуру создания контента, зарегистрировав разных создателей.Более того, вы можете добавить предпочтение каждому создателю для вашей собственной логики.Кроме того, я предлагаю вам инкапсулировать все данные, относящиеся к Контенту, точно так же, как класс «ContentDefinition» из поста другого.

В общем, вы должны знать, что всегда есть компромисс между гибкостью и эффективностью.Когда-нибудь ваше первое решение будет достаточно хорошим:)

0 голосов
/ 23 марта 2011
private void InitContent()
{
    Int64? id = !String.IsNullOrEmpty(Request.QueryString["id"])
                ? Convert.ToInt64(Request.QueryString["id"])
                : null;

    if (id != null && (Content = GetContent(id)) != null)
        ContentMode = ContentFrom.Query;
    else if(DefaultId != null && (Content = GetContent(DefaultId)) != null)
        ContentMode = ContentFrom.Default;
    else
        ContentMode = ContentFrom.None;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...