在C#中创建一个通用方法

104
我正在尝试将一些类似的方法合并成一个通用方法。 我有几个方法,它们返回查询字符串的值,如果该查询字符串不存在或格式不正确,则返回null。 如果所有类型都是本地可空的,那么这将非常容易,但是对于整数和日期,我必须使用可空泛型类型。
以下是我现在拥有的内容。 但是,如果数字值无效,它将传回0,而在我的场景中,这是一个有效值。 有人可以帮助我吗?谢谢!
public static T GetQueryString<T>(string key) where T : IConvertible
{
    T result = default(T);

    if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
    {
        string value = HttpContext.Current.Request.QueryString[key];

        try
        {
            result = (T)Convert.ChangeType(value, typeof(T));  
        }
        catch
        {
            //Could not convert.  Pass back default value...
            result = default(T);
        }
    }

    return result;
}

他正在移植一堆实现,所以调用旧功能,记住结果,调用新功能,记住结果,进行比较。现在使用一堆随机输入重复这个过程100次,就完成了! - Hamish Grubijan
对不起,我仍然不明白它在这种情况下如何适用。我仍在努力让函数正常工作。 - Mike Cole
看了答案,我有点困惑:你的调用者是使用 int 还是 int 作为 T 进行参数化的? - user24359
对我来说,似乎你应该允许该方法抛出异常,而不是在内部处理它。也许这只是我的想法,但有人可能会感到困惑,因为他们没有看到ChangeType失败时生成的异常,而他们的调用总是返回默认值。 - crush
6个回答

77
如果您指定了要返回的默认值,而不是使用default(T),会怎样呢?
public static T GetQueryString<T>(string key, T defaultValue) {...}

这也使得调用更加容易:

var intValue = GetQueryString("intParm", Int32.MinValue);
var strValue = GetQueryString("strParm", "");
var dtmValue = GetQueryString("dtmPatm", DateTime.Now); // eg use today's date if not specified

但缺点是您需要使用魔术值来表示无效/丢失的查询字符串值。


是的,这似乎比依赖整数的默认值更可行。我会记住这个的。虽然我仍然希望能让我的原始函数适用于所有类型,但我可能会使用非泛型函数。 - Mike Cole
为什么不返回除零以外的无效整数?您可以返回任何您想要的,既不是有效值,也没有特殊用途,比如null。您甚至可以创建自己的类型,称为InvalidInteger或类似名称。您正在为错误查询字符串返回null,对于无效整数也可以这样做,因此null只意味着“某些内容有问题,并且我没有值可提供给您”,并可能通过引用将reasonCode传递给函数? - Dan Csharpster
1
如何获取 long ? test 的值,其中默认值应为 null。 - Arshad

17
这个怎么样?将返回类型从T更改为Nullable<T>
public static Nullable<T> GetQueryString<T>(string key) where T : struct, IConvertible
        {
            T result = default(T);

            if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
            {
                string value = HttpContext.Current.Request.QueryString[key];

                try
                {
                    result = (T)Convert.ChangeType(value, typeof(T));  
                }
                catch
                {
                    //Could not convert.  Pass back default value...
                    result = default(T);
                }
            }

            return result;
        }

错误:类型“T”必须是非空值类型,才能将其用作泛型类型或方法“System.Nullable<T>”中的参数“T”。 - Mike Cole
1
你还需要指定 where T : struct - Aaronaught
@Mike C:你不应该得到相同的错误。修改后的代码肯定可以编译。 - Aaronaught
好的,我明白了。那么如果我想为字符串类型调用它会发生什么?现在它不会接受它。 - Mike Cole
@MikeC,不认为这是可能的,因为“string”是一个“可空”的值。 - Graviton
是的,但我希望一个函数可以处理字符串、整数、日期和 GUID 值。 - Mike Cole

17

我知道,我知道,但是...

public static bool TryGetQueryString<T>(string key, out T queryString)

4
“尝试模式”对于任何.NET开发人员来说应该都很熟悉。如果你问我的话,这并不是件坏事。在F#或.NET 4.0中,你可以使用Option(或Choice)来实现类似的功能。 - Christian Klauser
8
即使没有其他原因,我也会尽量避免这种情况,因为我讨厌必须"预先声明"输出变量,特别是如果它甚至从未被使用——这浪费了本来可以成为完美代码行的机会。 - Jay
1
我和Jay提到的原因一样,我讨厌Try模式。如果可能的话,我更喜欢一个通用函数,这也是我的最初目标。 - Mike Cole
12
做或不做,没有尝试!<尤达> - Rabbit
1
@Jay 在C#7中,您不再需要预先声明:https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/ - Holf
显示剩余3条评论

5

Convert.ChangeType()在.NET 2.0 BCL中不能正确处理可空类型或枚举(我认为在BCL 4.0中已经修复了)。不要让外部实现变得更加复杂,而是让转换器为您完成更多的工作。以下是我使用的一种实现:

public static class Converter
{
  public static T ConvertTo<T>(object value)
  {
    return ConvertTo(value, default(T));
  }

  public static T ConvertTo<T>(object value, T defaultValue)
  {
    if (value == DBNull.Value)
    {
      return defaultValue;
    }
    return (T) ChangeType(value, typeof(T));
  }

  public static object ChangeType(object value, Type conversionType)
  {
    if (conversionType == null)
    {
      throw new ArgumentNullException("conversionType");
    }

    // if it's not a nullable type, just pass through the parameters to Convert.ChangeType
    if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
      // null input returns null output regardless of base type
      if (value == null)
      {
        return null;
      }

      // it's a nullable type, and not null, which means it can be converted to its underlying type,
      // so overwrite the passed-in conversion type with this underlying type
      conversionType = Nullable.GetUnderlyingType(conversionType);
    }
    else if (conversionType.IsEnum)
    {
      // strings require Parse method
      if (value is string)
      {
        return Enum.Parse(conversionType, (string) value);          
      }
      // primitive types can be instantiated using ToObject
      else if (value is int || value is uint || value is short || value is ushort || 
           value is byte || value is sbyte || value is long || value is ulong)
      {
        return Enum.ToObject(conversionType, value);
      }
      else
      {
        throw new ArgumentException(String.Format("Value cannot be converted to {0} - current type is " +
                              "not supported for enum conversions.", conversionType.FullName));
      }
    }

    return Convert.ChangeType(value, conversionType);
  }
}

那么你的 GetQueryString<T> 实现可以是这样的:
public static T GetQueryString<T>(string key)
{
    T result = default(T);
    string value = HttpContext.Current.Request.QueryString[key];

    if (!String.IsNullOrEmpty(value))
    {
        try
        {
            result = Converter.ConvertTo<T>(value);  
        }
        catch
        {
            //Could not convert.  Pass back default value...
            result = default(T);
        }
    }

    return result;
}

5
你可以使用一种类似于Maybe Monad的方法(尽管我更喜欢Jay的答案)。
public class Maybe<T>
{
    private readonly T _value;

    public Maybe(T value)
    {
        _value = value;
        IsNothing = false;
    }

    public Maybe()
    {
        IsNothing = true;
    }

    public bool IsNothing { get; private set; }

    public T Value
    {
        get
        {
            if (IsNothing)
            {
                throw new InvalidOperationException("Value doesn't exist");
            }
            return _value;
        }
    }

    public override bool Equals(object other)
    {
        if (IsNothing)
        {
            return (other == null);
        }
        if (other == null)
        {
            return false;
        }
        return _value.Equals(other);
    }

    public override int GetHashCode()
    {
        if (IsNothing)
        {
            return 0;
        }
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        if (IsNothing)
        {
            return "";
        }
        return _value.ToString();
    }

    public static implicit operator Maybe<T>(T value)
    {
        return new Maybe<T>(value);
    }

    public static explicit operator T(Maybe<T> value)
    {
        return value.Value;
    }
}

您的方法应该如下所示:

    public static Maybe<T> GetQueryString<T>(string key) where T : IConvertible
    {
        if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
        {
            string value = HttpContext.Current.Request.QueryString[key];

            try
            {
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch
            {
                //Could not convert.  Pass back default value...
                return new Maybe<T>();
            }
        }

        return new Maybe<T>();
    }

0
我喜欢从这样的类开始: class settings { public int X {get;set;} public string Y { get; set; } // 根据需要重复

 public settings()
 {
    this.X = defaultForX;
    this.Y = defaultForY;
    // repeat ...
 }
 public void Parse(Uri uri)
 {
    // parse values from query string.
    // if you need to distinguish from default vs. specified, add an appropriate property

 }

这在数百个项目中都表现良好。您可以使用其他许多解析方案来解析值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接