【Unity】【C#】CSharp小技巧
标题虽然是小技巧,但本篇文章里还是记录了一些常用的功能,本文章的主要目的是方便日后快速查找
Where
Sort
时间复杂度为O(nlogn)
默认排序
1 | int[] numbers = { 5, 2, 8, 3, 1 }; |
自定义比较器
1 | string[] names = { "Alice", "Bob", "Charlie" }; |
多条件排序
1 | class Person |
部分排序
1 | List<int> numbers = new List<int> { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; |
拓展:
1 public delegate int Comparison<in T>(T x, T y);输入参数:
x和y是要比较的两个对象,类型为T。返回值:
- 一个整数,用来表示排序顺序:
- 负数:表示
x小于y(x排在y前面)。- 零:表示
x等于y(两者顺序不变)。- 正数:表示
x大于y(x排在y后面)。
1 x.CompareTo(y) = 20.CompareTo(10) // 返回1
1
2
3
4
5
6 List<int> numbers = new List<int> { 5, 2, 8, 3, 1 };
numbers.Sort((x, y) => x.CompareTo(y)); // 使用 Lambda 表达式定义 Comparison<T>
// 输出:1, 2, 3, 5, 8
numbers.ForEach(Console.WriteLine);
nameof():参数的名称(编译时确定好的常量),如果是泛型参数则输出Ttypeof().Name:类名的名称(编译时确定好的常量),如果是泛型参数则会输出泛型类实例化时的泛型类型(泛型函数的泛型参数的泛型参数)如果参数不是泛型参数,则与nameof()的效果一样obj.GetType().Name:实时获取obj的类型
测试代码:
1 | public class Item{} |

1 | public class ChainedClass |
1 | ChainedClass cc = new ChainedClass("coffee") // 构造函数 |
1 | public static class DotsExtensions |
重写+、-、*、/、|、$、!=、==,完全由自己定义,返回的类型甚至不需要是自身
1 | public struct CustomStruct // 结构体或类都可以 |
可以像数字一样直接使用+法了
1 | CustomStruct cs1 = new CustomStruct() { X = 1, Y = 2, Z = 3 }; |
1 | public struct PlayerMoveInput |
1 | var input = new PlayerMoveInput(); |
和隐式转换的语法一样,将implicit换成explicit就OK了
1 | public class PlayerMoveInput |
1 | var input = new PlayerMoveInput(); |
私有化构造函数
单例类,是不需要外界实例化的,可以将构造函数私有化,外界就无法实例化了。
1 | class Manager |
接口封装
不希望外界访问这个接口里所有的方法时,可以显示实现接口
1 | public interface IInterface |
1 | class ReadOnlyClass : IInterface // 创建一个只能读取,不能更改内容的类 |
ReadOnlyDictionary<TKey, TValue>()、ReadOnlyList<>、ReadOnlyCollection<>、ReadOnlySet<>都是使用显示实现接口的方法隐藏了修改的方法,使外界只能读取,不能修改
完整使用方法:
1 | class Manager |
拓展:
{ get; private set; }:在类内部还能重新setter
{ get; }:只能在构造函数中setter
自动排序列表
创建一个按照特定规则排列的列表:
例如,实例化一些Human,并且将他们按照age从大到小的方式放置一个列表中
1 | public interface IAnimal |
定义一个单例泛型类,用来做比较
1 | public class ReverseComparer<T> : IComparer<T> |
1 | public class HumanSelector : SortedList<int, IAnimal> // 继承`SortedList<int, IAnimal>`基类 |
使用:
1 | Human A = new Human(20, "A"); |
这样,我们就将排序封装起来了,使用者只需要使用Add将元素添加到排序列表中就OK了
遍历列表删除符合要求的元素
- 使用常规方法遍历列表直接删除元素会导致遍历循环顺序错误
- 复制列表会占用内存
最快速也是最简单的方法:反向遍历,从大索引号开始遍历
1 | List<int> list = new List<int>(){ 1, 2, 3, 4, 5 }; |
索引器
总得来说大致有三种用法
- 最普通的,针对字段复制了一个属性供外界使用,每次访问都会创建一个值类型的副本。
1 | private int _count; // 字段:内部使用 |
1 | private int _length; |
1 | public static int TotalCount { get; set; } // 属性:可读可写,这种方式实际上也隐式的创建了一个字段,如下图 |

- 使用了
ref修饰,使Run实际上是直接访问的_run这个字段,减少了一个复制的过程,提高性能
1 | [] |
- 可以让类像列表一样访问
1 | public string this[int index] |
甚至还可以将字典改成与python一样的用法:不过不推荐这种用法
1 | public class CustomizeDict |
default
常用default能使代码排版更好看,可以省去写判断表达式
值类型
1 | int number = defaule; // 默认值为0 |
引用类型
1 | string text = default; // 默认值为null |
C#在8.0后字符串和数组可以像python一样使用切片,时代码变的即简单又美观
值得注意的是列表不能使用这个小技巧
示例:

1 | var str = @"HelloWorld"; |
- 这里的
^和python里的-一样:反着数,^1表示最后一个字符d,^5表示倒数第五个字符W - 左闭右开,包含第一个参数,不包含第二个参数
这里面的所有简写rider都会提示,部分简写不建议使用,大大提高的代码的阅读效率
一个数介于两个数之间
1 | var ran = new Random(); |
值得注意一点的是,这个判断的两个数必须是常量值
如果是
list.Count,这里就用不了了
循环判断
为方便理解我先写成这样,解释一下每个方法的含义
1 | var list = new List<int>() { 5, 2, 7, 1, 9, 4 }; // 定义一个int列表 |
可简写成如下
1 | var list = new List<int>() { 5, 2, 7, 1, 9, 4 }; |
老实做法:
1
2
3
4
5
6 var list = new List<int>() { 5, 2, 7, 1, 9, 4 };
foreach (var i in list)
{
if (i > 3)
Console.WriteLine(i);
}这一样看上面的方法是不是即简洁又好看
但其实这些都是rider编辑器会提示或者直接帮我们转的,只要别说看不懂是什么意思就行了
赋值判断
A |= B:位或,左右两边的值进行位或,并将结果赋值给左边的变量。bool a |= 1 < 10;结果为True。位或:A |= B,如果B是true,那么A就是true;否则A的值不变result = A ?? B:如果A是null,返回B;否则返回Aresult ??= new List<int>():如果result是空,就进行复制操作;否则不做任何操作result = A is { age: 20 }:等价result = A != null && A.age == 20
用法
虽然这个应该是很常用的,但还是提一下
有两种写法,其输出结果是一样的
1 | int count = 100; |
第二种方法的性能要比第一种好
但是第一种情况在一些特殊的情况下使用有奇效,比如多语言设置、字符串替换等功能
具体可以看C#中字符串类的一些实用技巧及新手常犯错误这个大佬的
以上用的算的比较多的,所以不是今天的重点,今天的重点在格式化插值和格式化输出
1 | double i = 32.53728012341241562; |
10表示占位10个位置,当位置不够的时候在前面用空格补充F4表示保留4位小数,F不区分大小写
除了F外还有其他很多格式:
数字格式
数字格式都不区分大小写
F:Fiexd-point格式。显示小数后几位。$"{1234567.89:F2}"=>1234567.89C:货币格式。显示货币符号和千位分隔符。$"{1234.56:c}"=>¥1,234.56E:科学计数法格式。显示非常大或非常小的数字。$"{1231421432:e3}"=>1.231e+009N:与F类似,但是N能显示整数上的千位分隔符。$"{21432.537280:n3}"=>21,432.537G:自动选择合适的格式。P:百分比格式。将数值乘以100,并在结果后面加上百分号。$"{0.12345:p2}"=>12.35%R:常规格式。不带任何格式的方式显示数字,但保留足够的精度(怎么保留没有仔细研究,简单测试了几下,没有找出规律)
实用方法:
1 | float value = 546.5198; |
日期时间格式
与数字格式不同,日期时间格式需要区分大小写
注意:码代码的时候一定要拼写正确,是
DateTime不是DataTime年月日的排布
DateTime会自动更具文化设置
先看看普通的格式长什么样DateTime.Now => 2024/8/11 4:50:47
d:短日期格式。$"{date:d}"=>2024/8/11D:长日期格式。$"{date:D}"=>2024年8月11日t:短时间格式。$"{date:t}"=>4:57T:长时间格式。$"{date:T}"=>4:58:41f:(短)完整日期和时间。$"{date:f}"=>2024年8月11日 4:59F:(长)完整日期和时间。$"{date:F}"=>2024年8月11日 5:00:45M或m:月日格式。$"{date:m}"=>8月11日Y或y:年月格式。$"{date:m}"=>2024年8月
另外还有十分强大的自定义格式化
DateTime的格式化:
1 | DateTime date = DateTime.Now; |
输出:
1 | 2024年05月11日 05:05:09 上午 +08:00 |
TimeSpan的格式化:
1 | TimeSpan timeSpan = endTime - DateTime.Now; |
输出
1 | // 1天03时15分 |
Unity
使用Profiler.BeginSample(string)与Profiler.EndSample()方法捕获代码片段
1 | private void Update() |

在ugui中,给一个拥有Content Size Fitter的对象添加数个子节点后,该对象的大小并不一定会立刻改变成合适的大小,尤其是在该对象的父节点同样拥有Content Size Fitter组件的情况下。
解决方案:使用LayoutRebuilder.ForceRebuildLayoutImmediate(RectTransform)方法立刻刷新该对象的大小。
LayoutRebuilder.ForceRebuildLayoutImmediate(RectTransform)的作用
- 同步刷新布局:通常,Unity的UI布局(比如
VerticalLayoutGroup、HorizontalLayoutGroup等)在元素变动后,会在下一帧自动更新。如果你希望立刻看到变化效果,使用ForceRebuildLayoutImmediate可以强制马上重新布局。- 处理UI嵌套或动态变更:在有些场景下,通过代码动态增删控件、修改内容或尺寸,自动刷新不及时或不生效,此时调用本方法可确保UI排列正确。
拓展:
除了上面这个接口外,还有
LayoutRebuilder.MarkLayoutForRebuild(RectTransform)。
- Immediate:马上重新布局
- Mark:标记为需要重建,延迟到下一个布局更新阶段(效率更高)
一般可以两个都同时调用一下