简体   繁体   中英

Returning the first method that works, more elegant way?

Recently I've found myself writing methods which call other methods in succession and setting some value based on whichever method returns an appropriate value first. What I've been doing is setting the value with one method, then checking the value and if it's not good then I check the next one. Here's a recent example:

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;
}

Here the GetContent method should be returning null if the id isn't in the database. This is a short example, but you can imagine how this might get clunky if there were more options. Is there a better way to do this?

The null coalescing operator might have the semantics you want.

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

That's essentially the same as:

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

That is, q is the first non-null of W(), X(), Y(), or if all of them are null, then Z().

You can chain as many as you like.

The exact semantics are not quite like I sketched out; the type conversion rules are tricky. See the spec if you need the exact details.

You could also do something a little more sneaky, along the lines of this:

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;
}

That would help if you had many more sources, at the cost of increased complexity.

Personally, I find when I have lots of statements that are seemingly disparate, it's time to make some functions.

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;

  }
}

This way, you separate your intentions - first step, determine the content mode. Then, get the content.

I suggest you try some kind of Factory design pattern for this case. You can abstract the content create procedure by register different creators. Moreover, you can add preference on each creator for your own logic. Besides, I suggest you encapsulate all data related to Content just like "ContentDefinition" class from other's post.

In general, you need to know that there is always a trade off between flexibility and efficiency. Sometime your first solution is good enough:)

Ok, because I noticed a bit late that you actually wanted the ContentFrom mode as well, I've done my best to come up with a translation of your sample below my original answer


In general I use the following paradigm for cases like this. Search and replace your specific methods here and there :)

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); } }

Note how you can now make v!=null arbitrarily 'interesting' for your purposes.

Note also how lazy evaluation makes sure that the calculations are never done when _value or _alternative are set to 'interesting' values


Here is my initial attempt at putting your sample into this mold. Note how I added quite a lot of plumbing to make sure this actually compiles into standalone 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;

} }
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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM