关于返回 Null 值的问题

探讨了返回null值的问题,并提出使用IoC模式替代返回null值的方法,使代码更清晰且实体间能更好地通信。

 

我总感觉一个方法返回null值有问题。 当读了Misko Hevery关于how to think about OO的博客文章后,又让我想起这个问题。

我感觉返回null值是有问题的,它大量的被使用在一个方法有不同的返回类型时。 简单的用谷歌搜索一下“returning null”,你就会发现有建议把返回类型做成一个null对象。 返回一个Null对象在某些情况下是合适的,但并不适合当你需要向客户端传送两种不同的东西的情形。

用Misko重构的一段代码来说明这个问题。 他重构的是一段登录代码(我非常喜欢他的过程),这段代码大概是这个样子:

Cookie login(Ldap ldap) {
    if ( ldap.auth(user, password) )
      return new Cookie(user);
    return null;
}

从这段代码,可以看出两种情况(从结构上讲)

1. 如果认证通过, 客户端会被通知验证成功, 生成一个新的Cookie
2. 如果认证失败, 则通过返回的null值通知客户端

客户端的方法应该是什么样的?

public void authenticateUser(User user) {
    Cookie userCookie = user.login(ldap);
    if (userCookie == null) {
        //notify someone that auth failed
    } eles {
        //register them as logged in
    }
}

我们在两个地方做了相同的事情,只是在语法上有稍微的不同,每个地方,我们都要检查验证是否成功。 如果我们使用IoC(反向控制)模式,或“Tell Don’t Ask”模式或“Hollywood原则”,会如何?

Cookie login(Ldap ldap, AuthenticationRegistry authenticationRegistry) { if ( ldap.auth(user, password) ) authenticationRegistry.authSucceeded(new Cookie(user)); authenticationRegistry.authFailed(user); }

客户端:

public void authenticateUser(User user) {
    user.login(ldap,this);
}

public void authSucceeded(Cookie cookie) {
    //register them as logged in
}

public void authFailed(User user) {
    //register them as auth failed
}

新代码稍微有点复杂,但我感觉它很清晰,实现的更直接。 现在我们的两个实体能够相互通信,我们定义了它们通信的方式。 我喜欢Misko的重构,我只是更进了一步。 好坏可以再讨论,但我想,如果你遇到了这种需要返回两种情况的方法时,IoC是你应该的选择。

本文英文原文链接:Returning Null

翻译:http://justjavac.com/java/2012/05/18/returning-null.html

<think>好的,我现在需要帮用户解决如何修改之前的泛型方法,使其不返回null。用户之前询问的是如何编写一个泛型方法返回类型的默认,现在他们希望这个方法不返回null,也就是说,对于引用类型,可能不希望返回null而是某种默认实例。 首先,我需要回顾之前的解决方案。原来的方法是使用default关键字,对于引用类型会返回null类型返回默认。比如,string会得到null,而int是0,bool是false。但现在用户不希望得到null,可能需要为引用类型也提供一个默认实例。 接下来,我需要考虑如何区分类型和引用类型。在C#中,可以通过typeof(T).IsValueType来判断。如果是类型,继续使用default(T)没问题,但如果是引用类型,可能需要创建实例。不过,引用类型的默认本来就是null,如果用户不想返回null,可能需要调用构造函数。但问题在于,不是所有引用类型都有无参构造函数,这时候Activator.CreateInstance可能会失败,抛出异常。 所以,我需要提供一个安全的方式来创建实例。可能的解决方案是,对于引用类型,尝试使用无参构造函数创建实例,如果失败,可能还是返回null?但用户明确说不返回null,这就矛盾了。或者用户希望对于无法实例化的引用类型,返回某种默认,但具体需求不明确,可能需要进一步澄清。 不过用户可能的情况是,他们希望所有返回的实例都是非null的,即使引用类型也强制创建一个实例。但这样可能不可行,因为有些类没有公共构造函数,或者构造函数有参数。这时候如何处理? 或者,用户可能希望对于引用类型,如果无法创建实例,则返回null以外的某种默认,比如空对象模式。但空对象需要根据具体类型实现,无法通用处理。 或者,用户可能误解了“默认”的概念,认为默认不应为null,但实际上引用类型的默认确实是null。可能需要确认用户的实际需求,是否希望对于引用类型返回一个非null的实例,但这与C#的默认行为不同,需要特别处理。 假设用户确实需要无论类型如何都返回null,那么对于引用类型,可能需要使用Activator.CreateInstance来创建实例,但必须处理可能的异常。例如,在方法中添加try-catch,或者检查是否存在无参构造函数。如果存在,则创建实例,否则可能无法满足用户需求。 所以,可能的解决方案是:在泛型方法中,对于引用类型,检查是否有无参构造函数,如果有则创建实例,否则抛出异常或返回某种默认?但用户要求不返回null,所以可能需要抛出异常,或者提供替代方案。 另外,用户可能希望对于所有类型都返回null,包括引用类型。这时候,对于没有无参构造函数的引用类型,如何处理?可能需要使用FormatterServices.GetUninitializedObject来创建实例,但这会绕过构造函数,可能不安全。 总结可能的步骤: 1. 判断类型是否为类型,如果是,返回default(T)。 2. 如果是引用类型,尝试使用无参构造函数创建实例。 3. 如果无法创建(比如没有无参构造函数),如何处理?可能需要抛出异常,或者返回某种替代,但用户要求不返回null,所以必须处理。 或者,用户可能允许返回null,但希望在某些情况下避免,比如对于集合类型返回空集合而不是null。这时候可能需要针对特定类型进行处理,但这样会失去泛型的通用性。 或者,用户可能希望对于引用类型,返回一个默认实例,例如字符串返回空字符串而不是null,集合返回空集合等。这需要针对每个类型进行判断,但无法在泛型中全面覆盖。 这种情况下,可能需要使用条件判断来处理常见类型,但这样代码会变得复杂,且无法涵盖所有情况。例如: public static T GetNonDefault<T>() { if (typeof(T) == typeof(string)) return (T)(object)string.Empty; else if (typeof(T) is some collection type) return new T(); else return Activator.CreateInstance<T>(); } 但这种方法不适用于所有类型,特别是用户自定义的类。 所以,可能的结论是:在C#中,无法为所有引用类型提供一个非null的默认,因为有些类型无法被实例化。用户的需求可能无法完全满足,只能部分实现。需要向用户说明这一点,并询问是否有更具体的需求,或者是否允许在某些情况下抛出异常。</think>--- ### **问题分析** 当需要泛型方法不返回`null`时,需区分两种场景: 1. **类型**:始终有默认(如`int`的`0`) 2. **引用类型**:需强制返回实例而非`null` --- ### **解决方案** #### **核心思路** - 对引用类型**主动创建实例**,需满足: 1. 类型有**无参构造函数** 2. 处理无构造函数的特殊类型(如`string`) #### **代码实现** ```csharp public static T GetNonDefault<T>() { if (typeof(T).IsValueType) { return default(T); // 类型直接返回默认 } else { // 处理特殊引用类型 if (typeof(T) == typeof(string)) { return (T)(object)string.Empty; // 字符串返回 } // 尝试通过无参构造函数创建实例 return Activator.CreateInstance<T>(); } } ``` --- ### **使用示例与结果** ```csharp var list = GetNonDefault<List<int>>(); // 新建空列表(Count=0) var str = GetNonDefault<string>(); // 空字符串 "" var num = GetNonDefault<int>(); // 0 var obj = GetNonDefault<DataTable>(); // 新建DataTable实例 ``` --- ### **限制与注意事项** | 场景 | 行为 | 解决方案建议 | |--------------------------|-----------------------------|--------------------------| | **无无参构造函数的类** | 抛出`MissingMethodException` | 使用工厂模式或依赖注入容器 | | **抽象类/接口** | 抛出异常 | 返回具体实现类的实例 | | **无法实例化的类型** | 抛出异常 | 添加类型特判处理 | --- ### **增强版方案** 若需规避无参构造函数限制,可使用`FormatterServices`(需引用`System.Runtime.Serialization`): ```csharp using System.Runtime.Serialization; public static T GetNonDefaultEnhanced<T>() { if (typeof(T).IsValueType || typeof(T) == typeof(string)) { return GetNonDefault<T>(); // 复用基础逻辑 } else { // 跳过构造函数直接分配内存(慎用!) return (T)FormatterServices.GetUninitializedObject(typeof(T)); } } ``` ⚠️ 注意:此方法会**绕过构造函数**,可能导致对象状态不稳定,仅建议在反序列化等特殊场景使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值