标题虽然是小技巧,但本篇文章里还是记录了一些常用的功能,本文章的主要目的是方便日后快速查找
Linq
Where Sort 时间复杂度为O(nlogn)
默认排序
1 2 int [] numbers = { 5 , 2 , 8 , 3 , 1 };Array.Sort(numbers);
自定义比较器
1 2 string [] names = { "Alice" , "Bob" , "Charlie" };Array.Sort(names, (x, y) => y.Length.CompareTo(x.Length));
多条件排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Person { public string Name { get ; set ; } public int Age { get ; set ; } } var people = new List<Person>{ new Person { Name = "Alice" , Age = 30 }, new Person { Name = "Bob" , Age = 25 }, new Person { Name = "Charlie" , Age = 25 } }; people.Sort((x, y) => { int ageComparison = x.Age.CompareTo(y.Age); return ageComparison == 0 ? x.Name.CompareTo(y.Name) : ageComparison; });
部分排序
1 2 3 List<int > numbers = new List<int > { 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 }; numbers.Sort(2 , 5 , Comparer<int >.Default);
拓展:
1 public delegate int Comparison <in T >(T x, T y ) ;
输入参数 :
返回值 :
一个整数,用来表示排序顺序:
负数 :表示 x
小于 y
(x
排在 y
前面)。
零 :表示 x
等于 y
(两者顺序不变)。
正数 :表示 x
大于 y
(x
排在 y
后面)。
1 x.CompareTo(y) = 20. CompareTo(10 )
1 2 3 4 5 6 List<int > numbers = new List<int > { 5 , 2 , 8 , 3 , 1 }; numbers.Sort((x, y) => x.CompareTo(y)); numbers.ForEach(Console.WriteLine);
类型名称
nameof()
:参数的名称(编译时确定好的常量),如果是泛型参数则输出T
typeof().Name
:类名的名称(编译时确定好的常量),如果是泛型参数则会输出泛型类实例化时的泛型类型(泛型函数的泛型参数的泛型参数)如果参数不是泛型参数,则与nameof()
的效果一样
obj.GetType().Name
:实时获取obj的类型
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Item {}public class ItemA : Item {}public class ItemB : Item {}public class Test : MonoBehaviour { private Test<Item> test = new (); private void Awake () { Debug.Log($"{nameof (Item)} {nameof (ItemA)} {nameof (ItemB)} " ); Debug.Log($"{typeof (Item).Name} {typeof (ItemA).Name} {typeof (ItemB).Name} " ); test.FuncTO<Item>(); test.FuncTO<ItemA>(); test.FuncTO<ItemB>(); test.FuncT(new Item()); test.FuncT(new ItemA()); test.FuncT(new ItemB()); } } public class Test <T >{ public Test () { Debug.Log($"构造函数: {nameof (T)} {typeof (T).Name} " ); } public void FuncTO <TO >() { Debug.Log($"FuncTO: {nameof (TO)} {typeof (TO).Name} " ); } public void FuncT (T item ) { Debug.Log($"FuncT: {nameof (T)} {typeof (T).Name} {item.GetType().Name} " ); } }
链式方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ChainedClass { public string name; public int age; public ChainedClass (string name ) { this .name = name; } public ChainedClass SetAge (int age ) { this .age = age; return this ; } }
1 2 3 4 5 ChainedClass cc = new ChainedClass("coffee" ) .SetAge(10 ); Console.WriteLine(cc.Age); cc.SetAge(20 ); Console.WriteLine(cc.Age);
扩展方法
1 2 3 4 public static class DotsExtensions { public static float3 V2ToF3 (this Vector2 v2 ) => new float3(v2.x, v2.y, 0 ); }
Operator
重载运算符
重写+
、-
、*
、/
、|
、$
、!=
、==
,完全由自己定义,返回的类型甚至不需要是自身
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public struct CustomStruct { public float X, Y, Z; public static CustomStruct operator +(CustomStruct a, CustomStruct b) { return new CustomStruct { X = a.X + b.X, Y = a.Y + b.Y, Z = a.Z + b.Z }; } public override string ToString () { return $"({X} , {Y} , {Z} )" ; } }
可以像数字一样直接使用+法了
1 2 3 4 CustomStruct cs1 = new CustomStruct() { X = 1 , Y = 2 , Z = 3 }; CustomStruct cs2 = new CustomStruct() { X = 4 , Y = 5 , Z = 6 }; Console.WriteLine($"cs1 + cs2 = {cs1 + cs2} " );
implicit
隐式类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public struct PlayerMoveInput{ public float Value; public static implicit operator PlayerMoveInput (float value ) { return new PlayerMoveInput() { Value = value }; } public static implicit operator float (PlayerMoveInput input ) { return input.Value; } }
1 2 3 4 5 6 var input = new PlayerMoveInput();input = 1 ; Console.WriteLine(input);
explicit
显式类型转换
和隐式转换的语法一样,将implicit
换成explicit
就OK了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class PlayerMoveInput { public float Value; public static explicit operator PlayerMoveInput (float value ) { return new PlayerMoveInput() { Value = value }; } public static explicit operator float (PlayerMoveInput input ) { return input.Value; } }
1 2 3 4 5 var input = new PlayerMoveInput();input = (PlayerMoveInput)1 ; Console.WriteLine((float )input);
访问权限
私有化构造函数 单例类,是不需要外界实例化的,可以将构造函数私有化,外界就无法实例化了。
1 2 3 4 5 class Manager { public static Manager Instance = new Manager(); private Manager () { } }
接口封装 不希望外界访问这个接口里所有的方法时,可以显示实现接口
1 2 3 4 5 6 public interface IInterface { void Add () ; void Remove () ; int Get () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ReadOnlyClass : IInterface { void IInterface.Add() { throw new NotSupportedException("Readonly Not Supported Add" ); } void IInterface.Remove() { throw new NotSupportedException("Readonly Not Supported Remove" ); } public int Get () { return 1 ; } }
ReadOnlyDictionary<TKey, TValue>()
、ReadOnlyList<>
、ReadOnlyCollection<>
、ReadOnlySet<>
都是使用显示实现接口的方法隐藏了修改的方法,使外界只能读取,不能修改
完整使用方法:
1 2 3 4 5 6 7 8 9 class Manager { private Dictionary<string , string > _uiStack; public ReadOnlyDictionary<string , string > UIStack { get ; } public Manager () { UIStack = new ReadOnlyDictionary<string , string >(_uiStack); } }
拓展:
{ get; private set; }
:在类内部还能重新setter
{ get; }
:只能在构造函数中setter
列表
自动排序列表 创建一个按照特定规则排列的列表:
例如,实例化一些Human
,并且将他们按照age
从大到小的方式放置一个列表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface IAnimal { public int age { get ; set ; } public string name { get ; set ; } } public class Human : IAnimal { public int age { get ; set ; } public string name { get ; set ; } public Human (int age, string name ) { this .age = age; this .name = name; } }
定义一个单例泛型类,用来做比较
1 2 3 4 5 6 public class ReverseComparer <T > : IComparer <T >{ public static readonly ReverseComparer<T> Instance = new ReverseComparer<T>(); private ReverseComparer () { } public int Compare (T x, T y ) => Comparer<T>.Default.Compare(y, x); }
1 2 3 4 5 6 7 public class HumanSelector : SortedList <int , IAnimal > { public HumanSelector () : base (ReverseComparer<int >.Instance ) {} public void Add <T >(T man ) where T : IAnimal => Add(man.age, man); }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 Human A = new Human(20 , "A" ); Human B = new Human(25 , "B" ); Human C = new Human(30 , "C" ); HumanSelector humanSelector = new HumanSelector(); humanSelector.Add(C); humanSelector.Add(A); humanSelector.Add(B); humanSelector.ToList().ForEach(man => { Console.WriteLine($"{man.Key} , {man.Value.name} " ); });
这样,我们就将排序封装起来了,使用者只需要使用Add
将元素添加到排序列表中就OK了
遍历列表删除符合要求的元素
使用常规方法遍历列表直接删除元素会导致遍历循环顺序错误
复制列表会占用内存
最快速也是最简单的方法:反向遍历,从大索引号开始遍历
1 2 3 4 5 6 7 8 List<int > list = new List<int >(){ 1 , 2 , 3 , 4 , 5 }; for (int i = list.Count - 1 ; i >= 0 ; i--){ if (list[i] >= 4 ) { list.RemoveAt(i); } }
字段与属性
索引器 总得来说大致有三种用法
最普通的,针对字段复制了一个属性供外界使用,每次访问都会创建一个值类型的副本。
1 2 3 4 5 6 private int _count; public int Count { get => _count; set => _count = value / 2 ; }
1 2 private int _length;public int Length => _length;
1 public static int TotalCount { get ; set ; }
使用了ref
修饰,使Run
实际上是直接访问的_run
这个字段,减少了一个复制的过程,提高性能
1 2 3 [SerializeField ] private bool _run; public ref bool Run => ref _run;
可以让类像列表一样访问
1 2 3 4 5 public string this [int index]{ get => _items[index]; set => _items[index] = value ; }
甚至还可以将字典改成与python一样的用法:不过不推荐这种用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class CustomizeDict { private Dictionary<string , string > _data = new Dictionary<string , string >(); public object this [string key] { get => _data[key]; set { if (_data.ContainsKey(key)) _data[key] = value .ToString(); else _data.Add(key, value .ToString()); UpdateContent(); } } } CustomizeDict["speed" ] = 500 ;
default
常用default
能使代码排版更好看,可以省去写判断表达式
值类型
1 2 3 int number = defaule; double amount = default ; bool flag = default ;
引用类型
1 2 string text = default ; List<int > numbers = defalut;
字符串、数组切片
C#在8.0后字符串和数组可以像python
一样使用切片,时代码变的即简单又美观
值得注意的是列表不能使用这个小技巧
示例:
1 2 3 4 5 6 7 8 9 10 var str = @"HelloWorld" ;Console.WriteLine(str[1. .5 ]); Console.WriteLine(str[^1 ]); Console.WriteLine(str[..^0 ]); Console.WriteLine(str[6. .^2 ]); Console.WriteLine(str[^5. .^1 ]); Console.WriteLine(str[^7. .8 ]);
这里的^
和python
里的-
一样:反着数,^1
表示最后一个字符d
,^5
表示倒数第五个字符W
左闭右开,包含第一个参数,不包含第二个参数
简写判断语句
这里面的所有简写rider都会提示,部分简写不建议使用,大大提高的代码的阅读效率
一个数介于两个数之间 1 2 3 4 5 var ran = new Random();int i = ran.Next(20 ); if (i is < 10 and > 0 ) Console.WriteLine("i介于0和10之间" );
值得注意一点的是,这个判断的两个数必须是常量值
如果是list.Count
,这里就用不了了
循环判断 为方便理解我先写成这样,解释一下每个方法的含义
1 2 3 4 var list = new List<int >() { 5 , 2 , 7 , 1 , 9 , 4 }; IEnumerable<int > temp = list.Where(i => i > 3 ); list = temp.ToList(); list.ForEach(Console.WriteLine);
可简写成如下
1 2 3 4 5 var list = new List<int >() { 5 , 2 , 7 , 1 , 9 , 4 };foreach (var j in list.Where(i => i > 3 )) { Console.WriteLine(j); }
老实做法:
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;否则返回A
result ??= new List<int>()
:如果result是空,就进行复制操作;否则不做任何操作
result = A is { age: 20 }
:等价result = A != null && A.age == 20
格式化字符串
用法 虽然这个应该是很常用的,但还是提一下
有两种写法,其输出结果是一样的
1 2 3 int count = 100 ;Console.WriteLine("我今天吃了{0}顿" , count) Console.WriteLine($"我今天吃了{count} 顿" )
第二种方法的性能要比第一种好
但是第一种情况在一些特殊的情况下使用有奇效,比如多语言设置、字符串替换等功能
具体可以看C#中字符串类的一些实用技巧及新手常犯错误 这个大佬的
以上用的算的比较多的,所以不是今天的重点,今天的重点在格式化插值和格式化输出
1 2 3 double i = 32.53728012341241562 ;Console.WriteLine($"我的资产为{i, 10 :F4} 为所欲为" );
10
表示占位10个位置,当位置不够的时候在前面用空格补充
F4
表示保留4位小数,F
不区分大小写
除了F
外还有其他很多格式:
数字格式 数字格式都不区分大小写
F
:Fiexd-point格式。显示小数后几位。$"{1234567.89:F2}"
=> 1234567.89
C
:货币格式。显示货币符号和千位分隔符。$"{1234.56:c}"
=> ¥1,234.56
E
:科学计数法格式。显示非常大或非常小的数字。$"{1231421432:e3}"
=> 1.231e+009
N
:与F
类似,但是N
能显示整数上的千位分隔符。$"{21432.537280:n3}"
=> 21,432.537
G
:自动选择合适的格式。
P
:百分比格式。将数值乘以100,并在结果后面加上百分号。$"{0.12345:p2}"
=> 12.35%
R
:常规格式。不带任何格式的方式显示数字,但保留足够的精度(怎么保留没有仔细研究,简单测试了几下,没有找出规律)
实用方法:
1 2 float value = 546.5198 ;string str = $"{value :0. ##} " ;
日期时间格式 与数字格式不同,日期时间格式需要区分大小写
注意:码代码的时候一定要拼写正确,是DateTime
不是DataTime
年月日的排布DateTime
会自动更具文化设置
先看看普通的格式长什么样DateTime.Now
=> 2024/8/11 4:50:47
d
:短日期格式。$"{date:d}"
=> 2024/8/11
D
:长日期格式。$"{date:D}"
=> 2024年8月11日
t
:短时间格式。$"{date:t}"
=> 4:57
T
:长时间格式。$"{date:T}"
=> 4:58:41
f
:(短)完整日期和时间。$"{date:f}"
=> 2024年8月11日 4:59
F
:(长)完整日期和时间。$"{date:F}"
=> 2024年8月11日 5:00:45
M
或m
:月日格式。$"{date:m}"
=> 8月11日
Y
或y
:年月格式。$"{date:m}"
=> 2024年8月
另外还有十分强大的自定义 格式化
DateTime
的格式化:
1 2 DateTime date = DateTime.Now; Console.WriteLine($"{date:yyyy年mm月dd日 hh:mm:ss tt zzz} " );
输出:
1 2024年05月11日 05:05:09 上午 +08:00
TimeSpan
的格式化:
1 2 3 TimeSpan timeSpan = endTime - DateTime.Now; timeSpan.ToString(@"d\天hh\时mm\分" ); timeSpan.ToString(@"d\:h\:mm);
输出
Unity
性能测试
使用Profiler.BeginSample(string)
与Profiler.EndSample()
方法捕获代码片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void Update (){ Profiler.BeginSample("Invoke Action" ); for (int i = 0 ; i < 1000000 ; i++) { actionA?.Invoke(); } Profiler.EndSample(); Profiler.BeginSample("Invoke default Action" ); for (int i = 0 ; i < 1000000 ; i++) { actionB.Invoke(); } Profiler.EndSample(); }