C# 哪种结果模式最适合公共API?为什么?

C# 哪种结果模式最适合公共API?为什么?,c#,return-value,tuples,api-design,out-parameters,C#,Return Value,Tuples,Api Design,Out Parameters,在公共API中,有几种不同的常用模式可用于返回函数调用的结果。目前还不清楚哪种方法是最好的。对于最佳实践是否有普遍共识,或者至少有令人信服的理由说明一种模式优于其他模式 更新通过公共API,我指的是暴露于依赖程序集的公共成员。我不是专门指作为web服务公开的API。我们可以假设客户端正在使用.NET 我在下面编写了一个示例类来说明返回值的不同模式,并对它们进行了注释,表达了我对每种模式的关注 这是一个有点长的问题,但我相信我不是唯一一个考虑过这个问题的人,希望这个问题会引起其他人的兴趣 publ

在公共API中,有几种不同的常用模式可用于返回函数调用的结果。目前还不清楚哪种方法是最好的。对于最佳实践是否有普遍共识,或者至少有令人信服的理由说明一种模式优于其他模式

更新通过公共API,我指的是暴露于依赖程序集的公共成员。我不是专门指作为web服务公开的API。我们可以假设客户端正在使用.NET

我在下面编写了一个示例类来说明返回值的不同模式,并对它们进行了注释,表达了我对每种模式的关注

这是一个有点长的问题,但我相信我不是唯一一个考虑过这个问题的人,希望这个问题会引起其他人的兴趣

public class PublicApi<T>       //  I am using the class constraint on T, because 
    where T: class              //  I already understand that using out parameters
{                               //  on ValueTypes is discouraged (http://msdn.microsoft.com/en-us/library/ms182131.aspx)

    private readonly Func<object, bool> _validate;
    private readonly Func<object, T> _getMethod;

    public PublicApi(Func<object,bool> validate, Func<object,T> getMethod)
    {
        if(validate== null)
        {
            throw new ArgumentNullException("validate");
        }
        if(getMethod== null)
        {
            throw new ArgumentNullException("getMethod");
        }
        _validate = validate;
        _getMethod = getMethod;
    }

    //  This is the most intuitive signature, but it is unclear
    //  if the function worked as intended, so the caller has to
    //  validate that the function worked, which can complicates 
    //  the client's code, and possibly cause code repetition if 
    //  the validation occurs from within the API's method call.  
    //  It also may be unclear to the client whether or not this 
    //  method will cause exceptions.
    public T Get(object argument)
    {
        if(_validate(argument))
        {
            return _getMethod(argument);
        }
        throw new InvalidOperationException("Invalid argument.");
    }

    //  This fixes some of the problems in the previous method, but 
    //  introduces an out parameter, which can be controversial.
    //  It also seems to imply that the method will not every throw 
    //  an exception, and I'm not certain in what conditions that 
    //  implication is a good idea.
    public bool TryGet(object argument, out T entity)
    {
        if(_validate(argument))
        {
            entity = _getMethod(argument);
            return true;
        }
        entity = null;
        return false;
    }

    //  This is like the last one, but introduces a second out parameter to make
    //  any potential exceptions explicit.  
    public bool TryGet(object argument, out T entity, out Exception exception)
    {
        try
        {
            if (_validate(argument))
            {
                entity = _getMethod(argument);
                exception = null;
                return true;
            }
            entity = null;
            exception = null;   // It doesn't seem appropriate to throw an exception here
            return false;
        }
        catch(Exception ex)
        {
            entity = null;
            exception = ex;
            return false;
        }
    }

    //  The idea here is the same as the "bool TryGet(object argument, out T entity)" 
    //  method, but because of the Tuple class does not rely on an out parameter.
    public Tuple<T,bool> GetTuple(object argument)
    {
        //equivalent to:
        T entity;
        bool success = this.TryGet(argument, out entity);
        return Tuple.Create(entity, success);
    }

    //  The same as the last but with an explicit exception 
    public Tuple<T,bool,Exception> GetTupleWithException(object argument)
    {
        //equivalent to:
        T entity;
        Exception exception;
        bool success = this.TryGet(argument, out entity, out exception);
        return Tuple.Create(entity, success, exception);
    }

    //  A pattern I end up using is to have a generic result class
    //  My concern is that this may be "over-engineering" a simple
    //  method call.  I put the interface and sample implementation below  
    public IResult<T> GetResult(object argument)
    {
        //equivalent to:
        var tuple = this.GetTupleWithException(argument);
        return new ApiResult<T>(tuple.Item1, tuple.Item2, tuple.Item3);
    }
}

//  the result interface
public interface IResult<T>
{

    bool Success { get; }

    T ReturnValue { get; }

    Exception Exception { get; }

}

//  a sample result implementation
public class ApiResult<T> : IResult<T>
{
    private readonly bool _success;
    private readonly T _returnValue;
    private readonly Exception _exception;

    public ApiResult(T returnValue, bool success, Exception exception)
    {
        _returnValue = returnValue;
        _success = success;
        _exception = exception;
    }

    public bool Success
    {
        get { return _success; }
    }

    public T ReturnValue
    {
        get { return _returnValue; }
    }

    public Exception Exception
    {
        get { return _exception; }
    }
}
public class PublicApi//我在T上使用类约束,因为
其中T:class//我已经理解使用out参数
不鼓励在ValueType上使用{//(http://msdn.microsoft.com/en-us/library/ms182131.aspx)
私有只读函数验证;
私有只读Func_getMethod;
publicPublicAPI(Func验证,Func获取方法)
{
if(validate==null)
{
抛出新的ArgumentNullException(“验证”);
}
if(getMethod==null)
{
抛出新ArgumentNullException(“getMethod”);
}
_验证=验证;
_getMethod=getMethod;
}
//这是最直观的特征,但尚不清楚
//如果函数按预期工作,则调用者必须
//验证函数是否工作,这可能会使问题复杂化
//客户端的代码,并可能导致代码重复,如果
//验证在API的方法调用中进行。
//客户也可能不清楚这一点
//方法将导致异常。
公共T Get(对象参数)
{
如果(_验证(参数))
{
返回_getMethod(参数);
}
抛出新的InvalidOperationException(“无效参数”);
}
//这修复了前面方法中的一些问题,但是
//引入一个out参数,这可能会引起争议。
//这似乎也意味着该方法不会每次抛出
//一个例外,我不确定在什么情况下
//暗示是个好主意。
公共bool TryGet(对象参数,out T实体)
{
如果(_验证(参数))
{
实体=_getMethod(参数);
返回true;
}
实体=空;
返回false;
}
//这与上一个类似,但引入了第二个out参数
//任何潜在的例外都是明确的。
public bool TryGet(对象参数、out T实体、out异常)
{
尝试
{
如果(_验证(参数))
{
实体=_getMethod(参数);
异常=空;
返回true;
}
实体=空;
exception=null;//在这里抛出异常似乎不合适
返回false;
}
捕获(例外情况除外)
{
实体=空;
例外=例外;
返回false;
}
}
//这里的思想与“bool-TryGet(对象参数,out-T实体)”相同
//方法,但由于Tuple类不依赖out参数。
公共元组GetTuple(对象参数)
{
//相当于:
T实体;
bool success=this.TryGet(参数,out实体);
返回Tuple.Create(实体,成功);
}
//与上一个相同,但有一个明确的例外
公共元组GetTupleWithException(对象参数)
{
//相当于:
T实体;
例外情况;
bool success=this.TryGet(参数、out实体、out异常);
返回Tuple.Create(实体、成功、异常);
}
//我最终使用的模式是有一个通用的结果类
//我担心的是,这可能是一个简单的“过度工程”
//方法调用。我将接口和示例实现放在下面
公共IResult GetResult(对象参数)
{
//相当于:
var tuple=this.gettupeWithException(参数);
返回新的ApiResult(tuple.Item1、tuple.Item2、tuple.Item3);
}
}
//结果界面
公共接口IResult
{
布尔成功{get;}
T返回值{get;}
异常{get;}
}
//一个示例结果实现
公共类ApiResult:IResult
{
私人只读bool_成功;
私有只读T_返回值;
私有只读异常\u异常;
公共ApiResult(T返回值、bool成功、异常)
{
_returnValue=returnValue;
_成功=成功;
_例外=例外;
}
公共事业的成功
{
获取{return\u success;}
}
公共价值
{
获取{return\u returnValue;}
}
公共例外
{
获取{return\u exception;}
}
}
如果“公共API”指的是您无法控制的应用程序将使用的API,并且这些客户端应用程序将以多种语言/平台编写,我建议返回非常基本的类型(例如字符串、整数、小数),并使用类似JSON的内容来处理更复杂的类型

我认为您不能在公共API中公开泛型类,因为您不知道客户端是否支持泛型

就模式而言,我倾向于类似REST的方法,而不是SOAP。马丁·福勒(Martin Fowler)有一篇关于这意味着什么的高级文章:

  • 获取-如果验证失败,请使用此选项
    public static class MyClass
    {
    
      // not recomended:
      int static GetParams(ref thisObject, object Param1, object Params, object Param99)
      {
        const int ERROR_NONE = 0;
    
        try
        {
          ...
        }
        catch (System.DivideByZeroException dbz)
        {
          ERROR_NONE = ...;
          return ERROR_NONE;
        }
        catch (AnotherException dbz)
        {
          ERROR_NONE = ...;
          return ERROR_NONE;
        }
    
        return ERROR_NONE;
      } // method
    
      // recomended:
      int static Get(ref thisObject, object ParamsGroup)
      {
        const int ERROR_NONE = 0;
    
    
        try
        {
          ...
        }
        catch (System.DivideByZeroException dbz)
        {
          ERROR_NONE = ...;
          return ERROR_NONE;
        }
        catch (AnotherException dbz)
        {
          ErrorCode = ...;
          return ERROR_NONE;
        }
    
        return ERROR_NONE;
      } // method
    
    } // class