返回复杂对象或使用引用/输出参数是更好的做法吗?

时间:2010-07-28 19:59:14

标签: c# design-patterns methods

我正在组合一个应该评估输入的方法,如果满足所有条件则返回true,如果某些测试失败则返回false。如果出现故障,我还希望呼叫者可以获得某种状态消息。

我遇到过的设计包括返回bool并为消息使用out(或ref)参数,返回带有bool和string属性的(特别设计的)类的实例,或甚至返回指示pass的枚举或特定错误。什么是从方法中获取所有信息的最佳方法?这些中的任何一个“好”吗?有人有其他建议吗?

14 个答案:

答案 0 :(得分:13)

我通常会尝试返回一个复杂的对象,并在必要时回退到使用out参数。

但是你看看.NET转换中的TryParse方法,它们遵循返回bool的模式和转换后的值的out参数。所以,我不认为有参数是不好的 - 这实际上取决于你想要做什么。

答案 1 :(得分:4)

我更喜欢直接返回类型,因为某些.NET语言可能不支持ref和out参数。

以下是来自MS的good explanation的原因。

答案 2 :(得分:3)

返回对象更具可读性并且代码更少。没有性能差异,除了你自己跳过“输出参数”所需的箍。

答案 3 :(得分:2)

我想这取决于你对好的定义。

对我来说,我更喜欢用out参数返回bool。我认为它更具可读性。对于那个(在某些情况下更好)是Enum。作为个人选择,我倾向于不喜欢仅为消息数据返回课程;它根据我的口味将我正在做的事情抽象得太远了。

答案 4 :(得分:2)

此类事物很容易由Int32.Parse()Int32.TryParse()表示。要在失败时返回状态,要么返回一个值,如果它不需要您能够返回两个不同的类型,那么TryParse()中的out参数。返回一个专门的对象(在我看来)只是用不必要的类型混淆你的命名空间。当然,您也可以随时在其中抛出异常。就个人而言,我更喜欢TryParse()方法,因为您可以检查状态而不必生成异常,这很重。

答案 5 :(得分:2)

我强烈建议您返回一个对象(复杂或其他),因为您将来可能会发现需要返回其他信息,如果您使用简单返回类型和一个或两个引用的组合,你将不得不添加额外的参数,这些参数将不会使你的方法签名变得混乱。

如果您使用某个对象,您可以轻松修改它,以支持您需要的其他信息。

我会远离枚举,除非它非常简单,并且将来很可能不会改变。从长远来看,你会有更少的麻烦,如果你返回一个物体,事情就会变得更简单。如果你给返回'对象'他们自己的命名空间,那么它会使命名空间变得混乱的论点是一个弱点。但是对于他们自己的命名空间也是如此。

(注意如果你想使用枚举,只需将它放在你的返回对象中,这将为你提供简单的枚举器,以及一个能够满足你所需要的对象的灵活性。)

答案 6 :(得分:2)

编辑:我在顶部添加了我的观点摘要,以便于阅读:

  • 如果你的方法返回多个逻辑上都属于同一个“东西”的数据,那么无论你是将这个对象作为返回值还是输出返回,都要将它们组成一个复杂的对象。参数。

  • 如果您的方法除了数据之外还需要返回某种状态(成功/失败/更新了多少记录/等),那么请考虑将数据作为输出参数返回并使用返回值返回状态

此问题适用的情况有两种不同:

  1. 需要返回包含多个属性的数据

  2. 需要返回数据以及用于获取该数据的操作的状态

  3. 对于#1,我的观点是,如果你有多个属性组成的数据都在一起,那么它们应该作为一个类或结构组成一个单一的类型,并且应该返回一个单独的对象作为返回方法的值或输出参数。

    对于#2,我认为这是输出参数真正有意义的情况。从概念上讲,方法通常执行一个动作;在这种情况下,我更喜欢使用方法的返回值来指示操作的状态。这可能是一个简单的布尔值来表示成功或失败,或者它可能是更复杂的东西,如枚举或字符串,如果有多个可能的状态来描述该方法执行的操作。

    如果使用输出参数,我会鼓励一个人只使用一个(参见第1点),除非有特定的理由使用多个。不要仅使用多个输出参数,因为您需要返回的数据包含多个属性。如果您的方法的语义具体指明它,则仅使用多个输出参数。下面是一个例子,我认为多个输出参数是有意义的:

        // an acceptable use of multiple output parameters
        bool DetermineWinners(IEnumerable<Player> players, out Player first, out Player second, out Player third)
        {
            // ...
        }
    

    相反,这是一个例子,我认为多个输出参数没有意义。

        // Baaaaad
        bool FindPerson(string firstName, string lastName, out int personId, out string address, out string phoneNumber)
        {
            // ...
        }
    

    数据的属性(personId,address和phoneNumber)应该是方法返回的Person对象的一部分。更好的版本如下:

        // better
        bool FindPerson(string firstName, string lastName, out Person person)
        {
            // ...
        }
    

答案 7 :(得分:1)

我更喜欢一个对象而不是parms主要是因为你可以在属性的“Set”中检查有效值。

这在Web应用程序中经常被忽略,并且是一种基本的安全措施。

OWASP指南规定,每次分配值时都应验证值(使用白名单验证)。如果你要在多个地方使用这些值,那么创建一个对象或结构来容纳值并检查那里更容易,而不是每次在代码中都有一个out parm时检查。

答案 8 :(得分:1)

这可能是主观的。

但与往常一样,我会说它几乎取决于并且没有一种“正确”的方式来做到这一点。例如,字典使用的TryGet()模式返回bool(通常在if中消耗),有效返回类型为out。在这种情况下,这非常有意义。

但是,如果你枚举你得到的项目KeyValuePair<,> - 这也是有意义的,因为你可能需要将密钥和值都作为一个“包”。

在您的具体情况下,我可能想要实际期望bool作为结果并传入一个可选的(null允许的)ICollection<ErrorMessage>实例,该实例会收到错误({{1如果这足够的话,可能只是ErrorMessage。这样可以报告多个错误。

答案 9 :(得分:1)

我使用枚举,但将它们用作标志/位模式。这样我可以指出多个失败的条件。

[Flags]
enum FailState
{
    OK = 0x1; //I do this instead of 0 so that I can differentiate between
              // an enumeration that hasn't been set to anything 
              //and a true OK status.
    FailMode1 = 0x2;
    FailMode2 = 0x4;
}

那么在我的方法中我只是这样做

FailState fail;

if (failCondition1)
{
    fail |= FailState.FailMode1;
}
if (failCondition2)
{
    fail |= FailState.FailMode2;
}

if (fail == 0x0)
{
    fail = FailState.OK;
}

return fail;

关于这种方法的烦人的事情是确定枚举中是否设置了一个位是这样的

if (FailState.FailMode1 == (FailState.FailMode1 && fail))
{
    //we know that we failed with FailMode1;
}

我使用扩展方法在枚举上给自己一个IsFlagSet()方法。

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }
}

我在这里从另一个答案复制了这个代码,但是我不知道答案在哪里......

这让我只能说

if (fail.IsFlagSet(FailState.Ok))
{
    //we're ok
}

答案 10 :(得分:0)

在这种情况下,TryParse(...)方法不是最好的例子。 TryParse仅在解析数字时发出信号,但没有说明解析失败的原因。它不需要,因为它失败的唯一原因是“格式错误”。

如果你想出的方法没有明显好的方法签名,你应该退后一步,问问自己这种方法是否可能做得太多。

您正在进行哪些测试?他们有关系吗?是否存在由于完全不同的无关原因导致失败的相同方法的测试,以便向用户/消费者提供有意义的反馈?

会重构您的代码帮助吗?您是否可以逻辑地将测试分组为单独的方法,这些方法只能由于一个原因(单一用途原则)而失败,并相应地从您的原始呼叫站点调用它们而不是您正在考虑的一种多用途方法?

失败的测试是否只是需要记录到某个日志文件的正常情况,还是必须以某种方式通知用户可能会停止正常的应用程序流?如果是后者,是否可以使用自定义异常并相应地捕获它们?

有很多可能性,但我们需要更多关于您要做的事情的信息,以便为您提供更精确的建议。

答案 11 :(得分:0)

我们使用Request和Response对象执行与WCF服务非常相似的操作,并且异常会一直冒泡(不在方法本身中处理)并由自定义属性(用PostSharp编写)处理,该属性捕获异常并且返回'失败'响应,例如:

[HandleException]
public Response DoSomething(Request request)
{
   ...

   return new Response { Success = true };
}

public class Response
{
   ...
   public bool Success { get; set; }
   public string StatusMessage { get; set; }
   public int? ErrorCode { get; set; }
}

public class Request
{
   ...
}

属性中的异常处理逻辑可以使用Exception消息来设置StatusMessage,如果您的应用程序中有一组自定义异常,您甚至可以在可能的情况下设置ErrorCode。

答案 12 :(得分:0)

我认为这取决于复杂程度,操作是否会失败,导致......什么?

对于TryParse示例,您通常无法返回无效值(例如,没有“无效”int这样的东西)并且绝对不能返回空值类型(相应的Parse方法不会返回Nullable<T>)。此外,返回Nullable<T>需要在呼叫站点引入Nullable<T>变量,条件检查以查看值是null,然后转换为T type,必须复制运行时检查以在返回实际的非null值之前查看该值是否为null。将结果作为out参数传递并指示返回值成功或失败(显然,在这种情况下,您不关心为什么它失败,这只是它的效率较低) 确实)。在以前的.Net运行时中,唯一的选择是Parse方法,它们会引发任何类型的故障。捕获.Net异常非常昂贵,尤其是返回指示状态的true / false标记。

就您自己的界面设计而言,您需要考虑为什么您需要一个out参数。也许您需要从函数返回多个值,并且不能使用匿名类型。也许您需要返回值类型,但根据输入无法生成合适的默认值。

outref参数不是no-nos,但在使用之前,应仔细考虑您的界面是否有必要。在超过99%的情况下,标准的引用返回应该足够了,但我会在不必要地定义新类型之前使用outref参数来满足返回“单个”对象的要求多个值。

答案 13 :(得分:0)

  

“我正在整理一种方法   应该评估输入和   如果满足所有条件,则返回true   如果某些测试失败,则为false。我也是   喜欢某种地位   如果消息可用于呼叫者   有一次失败。“

如果您的方法有关于输入的条件,并且如果它们不符合,您可以考虑抛出适当的异常。通过抛出适当的例外,调用者将知道究竟出了什么问题。这是一种更加可维护和可扩展的方法。由于显而易见的原因,返回错误代码不是一个好的选择。

“我遇到的设计包括返回bool并为消息使用out(或ref)参数,返回具有bool和string属性的(特别设计的)类的实例,甚至返回枚举指示传递或特定错误。“

  1. 复杂对象是最明显的选择。 (类或结构)

  2. Out parameter:“考虑参数的一种方法是它们就像方法的附加返回值。当方法返回多个值时,它们非常方便,在本例中为firstName和但是,参数可以被滥用。如果你发现自己编写了一个包含许多参数的方法,那么你应该考虑重构代码。一种可能的解决方案是将所有返回值打包到一个结构中“。 太多或参考参数指向设计缺陷太多。

  3. Tuple:“在一些结构中将一堆其他不相关的数据组合在一起,比一个类更轻量级”

  4. 必读:More on tuples