简体   繁体   中英

Is This Proper Use of GOTO in C#

I'm tracing some code, and a list is sent to the below, to remove certain items from the list.

Is this the proper way to use the goto? Is it even necessary? Wouldn't you just edit the second if to be an else if, and continue processing the list without the need for a goto? (This is the first time I've ever seen goto outside of BASIC.)

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
    {
        if (src == null)
            return null;
    checkAB01:
        for (int i = 0; i < src.Count; i++)
        {
            ClientEntity info = src[i];
            if (info.ProductNumber != null && info.ProductNumber.ToLower().Trim().Length > 0
                    &&
                    info.CancelledByReliantSyncAB01 != null && info.CancelledByReliantSyncAB01.Value == true
                )
            {
                src.Remove(info);
                goto checkAB01;
            }

            if ((info.PartnerContract == null || info.PartnerContract.Trim().Length == 0)
                &&
                (info.ProductNumber == null || info.ProductNumber.Trim().Length == 0))
            {
                src.Remove(info);
                goto checkAB01;
            }
        }
        return src;
    }

How about LINQ?

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
{
    if (src == null)
        return null;

    return (from info in src.AsEnumerable<ClientEntity>()
            where !(!String.IsNullOrWhiteSpace(info.ProductNumber) &&
                    info.CancelledByReliantSyncAB01 == (bool?)true)
            where !(String.IsNullOrWhitespace(info.PartnerContract) &&
                    String.IsNullOrWhiteSpace(info.ProductNumber))
            select info).ToList();
}

No, this isn't a proper way to use goto, it's just a substitute for someone who doesn't know how to properly remove items from a list. (Iterate backwards to prevent skipping elements)

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
{
    if (src == null)
        return null;

    for (int i = src.Count - 1; i >= 0; i--)
    {
        ClientEntity info = src[i];
        if (info.ProductNumber != null && info.ProductNumber.ToLower().Trim().Length > 0
                &&
                info.CancelledByReliantSyncAB01 != null && info.CancelledByReliantSyncAB01.Value == true
            )
        {
            src.RemoveAt(i);
        }

        if ((info.PartnerContract == null || info.PartnerContract.Trim().Length == 0)
            &&
            (info.ProductNumber == null || info.ProductNumber.Trim().Length == 0))
        {
            src.RemoveAt(i);
        }
    }
    return src;
}

I think whoever wrote this code did not know how to remove items from the iterated collection. The reason for those gotos was that once an item is deleted, the collection becomes smaller which can cause iteration errors.

A better day to do this is to do a reverse for loop. This way you do not have to reiterate through the whole list after a deletion. The code below will work just fine.

for (int i = src.Count - 1; i >= 0; i--) {
    src.Remove(src[i]);
}

As others have said, this is a poor usage of goto (which should rarely , if ever, be used)

Overall though, the implementation is terribly inefficient. Looks like it loops through from 0..N until it removes something, then starts again from 0..N (now 1 less) until it removes something, and so on. Even worse, the Remove call again goes from 0..N looking for that item to remove.

If it finds nothing to remove, it returns. Better to just do a reverse for loop removing entries with RemoveAt then returning.

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
{
    if (src == null)
        return null;

    for (int i = src.Count - 1; i >= 0; i--)
    {
        ClientEntity info = src[i];
        if (info.ProductNumber != null && info.ProductNumber.ToLower().Trim().Length > 0
            &&
            info.CancelledByReliantSyncAB01 != null && info.CancelledByReliantSyncAB01.Value == true)
        {
            src.RemoveAt(i);
        }
        else if ((info.PartnerContract == null || info.PartnerContract.Trim().Length == 0)
            &&
            (info.ProductNumber == null || info.ProductNumber.Trim().Length == 0))
        {
            src.RemoveAt(i);
        }
    }

    return src;
}

Also, I added an elseif there: seems dangerous to do another if check that could potentially be true and try to re-remove the same item (especially after changing the indices).

EDIT: If we're talking about readable usable code, I'd move out the checks to a separate method anyway:

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
{
    if (src == null)
        return null;

    for (int i = src.Count - 1; i >= 0; i--)
    {
        if (ShouldClientNotSendBack(src[i]))
            src.RemoveAt(i);
    }

    return src;
}

private static bool ShouldClientNotSendBack(ClientEntity info)
{
    if (!String.IsNullOrWhiteSpace(info.ProductNumber) && info.CancelledByReliantSyncAB01 == true)
    {
        return true;
    }

    if (!String.IsNullOrWhiteSpace(info.PartnerContract))
    {
        return true;
    }

    return false;
}

Might even consider tweaking that ShouldClientNotSendBack method and/or name (perhaps even move the two sets of if checks to individual methods with clear names), but this I think is a significant improvement overall.

EDITx2: In fact, I would strongly consider usage of the method. The method is clearly returning an IList<ClientEntity> while taking in an input collection, which typically communicates to developers that this is creating a new list when in fact it's actually mutating the existing list and returning the same list instance. Either have it return a new list (thus you should change the loop code to add to a new list instead of removing from the existing) or remove the return type so it's more apparent that it's mutating the passed list argument.

As I stated in my comment, there is no "proper" way to use goto in C#. The keyword is, by its very definition, a kludge; it's a throwback to C/C++, which included it as a "just in case", should a developer want to translate line-by-line a program in ASM or BASIC or other language without defined code blocks, which hide the "jumps" used to get into and between them. Any algorithm that uses it can be refactored to not have to do so, and the resulting algorithm will be more readable.

In this case:

public static IList<ClientEntity> FilterNoNeedSendBackToClients(IList<ClientEntity> src)
{
    if (src == null)
        return null;

    for (int i = src.Count-1; i>=0; i--)
    {
        ClientEntity info = src[i];
        if (info.ProductNumber != null && info.ProductNumber.ToLower().Trim().Length > 0
                &&
                info.CancelledByReliantSyncAB01 != null && info.CancelledByReliantSyncAB01.Value == true
            )
        {
            src.Remove(info);
            continue;
        }

        if ((info.PartnerContract == null || info.PartnerContract.Trim().Length == 0)
            &&
            (info.ProductNumber == null || info.ProductNumber.Trim().Length == 0))
        {
            src.Remove(info);
            continue;
        }
    }
    return src;
}

As Chris Sinclair's answer shows, because of the "either/or" implicit structure of the conditionals in the loop, the continue statements aren't necessary, but I like them because they show the coder that nothing from that point to the end of the loop will be run, without them having to read the rest of it to determine that.

If you wanted to, you could run through the list front to back, and if an item is removed, decrement i before continuing so that the loop will maintain its current position in the list. Some may say not to do it this way because the manipulation of the counter makes it harder to understand the algorithm (and because determining the count of the list on each iteration is slightly slower), but it's still far better in both performance and readability than the goto .

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