C#开发者必知的10个冷门特性:让你的代码更高效、更智能

作者:微信公众号:【架构师老卢】
2-22 8:7
19

你可能已经听说过 C# 的强类型系统,或者它是微软对 Java 的回应。但今天,我们将深入探讨一些即使是经验丰富的开发者也可能不知道的 C# 奇妙特性。

让我们一起来探索这门复杂语言中一些令人惊叹的特性,它们可能会改变你编写代码的方式。


1. 神秘的 default 操作符有一个秘密身份

你是否注意到 default(T)default 操作符并不完全相同?这里有一些有趣的东西:

var x = default; // 类型推断的魔法!
Console.WriteLine(x.GetType()); // 编译错误 - x 实际上是空的

int y = default;
Console.WriteLine(y); // 0

上下文关键字 default 比看起来更聪明。它从上下文中推断类型,使你的代码更简洁,同时保持类型安全。不需要显式的类型参数!


2. 你的属性可以时间旅行

C# 属性有一个隐藏的特性:它们可以检测自己是否在构造函数中被使用。

public class TimeManager
{
    private DateTime _createdAt;
    public DateTime CreatedAt
    {
        get => _createdAt;
        init
        {
            if (RuntimeHelpers.IsReferenceOrContainsReferences<TimeManager>())
                _createdAt = value;
            else
                throw new InvalidOperationException("Time travel not allowed!");
        }
    }
}

init 访问器实际上可以检查它是否在对象初始化期间被调用。这使得一些非常复杂的不可变性模式成为可能。


3. 字符串实际上是数组(某种程度上)

虽然字符串是不可变的引用类型,但 C# 允许你像数组一样索引它们。但这里有一个转折:

string text = "Hello";
Console.WriteLine(text[1]); // 'e'
Span<char> span = text.AsSpan();
span[1] = 'a'; // 编译错误!

字符串实现了 IEnumerable<char> 并支持索引,但它们并不是真正的数组。AsSpan 方法通过防止修改揭示了它们的真实本质。


4. 编译器是你的秘密朋友

C# 有一种叫做“基于模式的固定语句”的东西,可以使处理非托管内存变得异常优雅:

public ref struct PinnedArray<T> where T : unmanaged
{
    private Span<T> _span;
    public ref T GetPinnableReference() => ref _span[0];
}

编译器识别这种模式,并启用固定语句支持,而无需任何不安全代码。这就像与编译器有一个秘密握手!


5. 接口可以有静态成员

自 C# 8.0 以来,接口可以有静态成员,包括字段!但这里有一个陷阱:

public interface IProcessor
{
    static readonly int MaxThreads = Environment.ProcessorCount;
    static abstract void Initialize();
    static virtual void Shutdown() => Console.WriteLine("Shutting down...");
}

你甚至可以在接口中拥有静态抽象成员。这就像接口在没人注意的时候获得了超能力。


6. 堆并不总是你想的那样

值类型并不总是存储在栈上。看看这个:

struct LargeStruct
{
    private byte[] data = new byte[1024];
}

LargeStruct local = new(); // 这个存储在哪里?

结构体本身可能在栈上,但它的 byte[] 字段却存储在堆上。栈和堆之间的界限比大多数开发者意识到的要微妙得多。


7. 模式匹配可以看到未来

C# 的模式匹配非常强大,它可以查看尚未构造的对象的内部:

object obj = null;
if (obj is string { Length: > 5 } str)
{
    Console.WriteLine(str);
}

模式匹配器会同时检查 null、类型和属性条件,而不会抛出 NullReferenceException。这就像薛定谔的模式匹配!


8. 局部函数是闭包忍者

局部函数可以从它们的包含作用域中捕获变量,但有一个转折:

void ProcessData(int threshold)
{
    int multiplier = 2;
    local();

    void local()
    {
        Console.WriteLine(threshold * multiplier);
    }
    
    multiplier = 3; // 这会影响局部函数!
}

它们可以看到并修改来自父作用域的变量,即使是在它们定义之后。这就像它们对周围上下文有 X 光透视能力。


9. 记录有一个隐藏的生命

记录不仅仅是具有自动属性生成的类:

public record Person(string Name, int Age)
{
    public void Deconstruct(out string name, out int age) => 
        (name, age) = (Name, Age);
}

它们自动实现了 IEquatable<T>,支持解构,并通过编译器的巧妙魔法和运行时支持处理复制构造。


10. using 语句比你想象的更灵活

using 语句不再仅仅用于 IDisposable

using var scope = new TransactionScope();
// 这里的代码
// 作用域在当前块结束时自动释放!

自 C# 8.0 以来,你可以使用简化的 using 声明,它会在作用域结束时自动释放。不再需要嵌套块!

相关留言评论
昵称:
邮箱:
阅读排行