unity的语言问题记录(委托相关)
NGUI跟随鼠标实例位置
// 获取鼠标位置
Vector3 mousePos = Input.mousePosition;
// 将屏幕坐标转换为NGUI的世界坐标
Vector3 worldPos = UICamera.mainCamera.ScreenToWorldPoint(mousePos);
worldPos.z = 0; // 确保图片在UI层
// 设置图片位置
instantiatedImage.transform.position = worldPos;
// 将屏幕坐标转换为NGUI的世界坐标 Vector3 worldPos = UICamera.mainCamera.ScreenToWorldPoint(mousePos); worldPos.z = 0;
List
在处理List
集合时,foreach
循环和for
循环配合list[i]
的使用确实在大多数情况下是功能等效的,但它们在某些方面还是有一些区别和适用场景:
1. foreach
循环:
foreach (var item in myList)
{
Console.WriteLine(item);
}
- 可读性:
foreach
循环通常更简洁和易读,特别适合需要遍历整个集合而不修改集合的场景。 - 避免索引错误:由于不使用索引,
foreach
循环减少了由于错误使用索引而导致的异常。 - 更安全:
foreach
不允许在遍历过程中修改集合(如添加或删除元素),因此更安全,但这也意味着它不适用于需要修改集合的场景。
2. for
循环:
for (int i = 0; i < myList.Count; i++)
{
Console.WriteLine(myList[i]);
}
- 灵活性:
for
循环允许更灵活的操作。你可以通过索引访问和修改集合中的元素,还可以轻松地跳过或重复某些元素。 - 部分遍历:你可以通过修改索引变量的值,轻松实现部分遍历,或者从特定位置开始遍历集合。
- 支持反向遍历:通过调整索引变量的初始值和步长,
for
循环可以轻松实现从集合末尾向前遍历。 - 修改集合:在
for
循环中,可以安全地对集合进行添加、删除或替换操作,只需小心管理索引。
3. 性能:
- 对于
List
这种基于数组的数据结构来说,foreach
和for
在性能上几乎没有差别,因为foreach
在内部使用了类似索引的方式来遍历集合。 - 然而,在某些情况下,
for
循环可能略微优于foreach
,尤其是当你频繁访问元素索引时,因为for
循环可以避免foreach
循环在每次迭代中创建的额外迭代器对象。
4. 适用场景:
foreach
:适用于遍历整个集合、不需要知道索引或者不打算修改集合的场景。for
:适用于需要部分遍历、按特定顺序遍历、反向遍历,或者需要修改集合内容的场景。
总结:
- 如果你只是遍历集合并读取元素,
foreach
和for
几乎没有区别,foreach
更简洁。 - 如果需要修改集合内容、实现复杂的遍历逻辑,或者部分遍历,
for
循环可能会更适合。
元组解构赋值
元组解构赋值,这种语法允许在一行代码中交换两个变量的值。这种语法在C# 7.0及以上的版本中引入,并且可以用于元组的解构和多变量的同时赋值。
例如,这段代码:
(keys[index1], keys[index2]) = (keys[index2], keys[index1]);
在这里,(keys[index1], keys[index2])
是一个元组,包含两个元素。右边的 (keys[index2], keys[index1])
也是一个元组。C# 的解构赋值语法允许你直接将右边元组的值分配给左边的对应位置的变量,这样你可以很方便地交换两个变量的值。
为什么用元组解构赋值?
- 简洁:不需要像传统方法那样使用临时变量来交换两个值。
- 直观:在同一行代码中直接交换多个变量的值,代码更加清晰。
传统的交换方式
在引入这个语法之前,交换两个变量通常是这样实现的:
var temp = keys[index1];
keys[index1] = keys[index2];
keys[index2] = temp;
元组解构赋值提供了一种更加简洁的方式来实现相同的操作。
这个语法虽然在C#中相对较新,但它在其他编程语言如Python中早已广泛使用。
C#中的这种新特性,同样有C# 7.0及以后版本引入的其他功能,例如本地函数、模式匹配等。
var
在C#中,var
是一个隐式类型变量声明的关键字,它允许编译器根据右侧赋值的类型自动推断变量的类型。当你使用var
来声明一个变量时,编译器会根据赋值的表达式确定该变量的实际类型。
举个例子,如果你将一个List<int>
赋值给一个var
变量,编译器会自动推断出该变量的类型是List<int>
:
var myList = new List<int> { 1, 2, 3 };
在这个例子中:
var myList
声明了一个变量myList
。- 右边的赋值是
new List<int> { 1, 2, 3 }
,这是一个List<int>
类型的对象。 - 因此,编译器会将
myList
的类型推断为List<int>
。
这意味着 myList
是一个 List<int>
类型的变量,并且你可以像操作普通 List<int>
变量一样操作它。
关键点:
var
并不是一个动态类型,编译时它的类型就已经确定了。在编译后,var
就会被替换为实际的类型。var
只能用于局部变量声明,并且必须在声明时进行初始化,编译器才能推断类型。
因此,var
可以存储一个 List
,因为编译器能够从赋值的表达式中推断出 List
的类型。
除了谓词还可以在什么情况下使用
在 C# 中,除了谓词(如 Any
、Where
等 LINQ 方法)之外,lambda 表达式
可以用于多种场景。以下是一些常见的应用场景:
1. 委托
Lambda 表达式
可以用来简化委托的使用。委托是一种引用方法的类型,在 C# 中可以使用 lambda 表达式
来创建匿名方法并将其赋值给委托。
示例:
Func<int, int, int> add = (x, y) => x + y;
int result = add(3, 4); // result 为 7
在这个例子中,add为声明的func委托名,=>后的部分直接等价于{return x+y;}
至于谓词在List.Any中的使用,因为谓词可以说是返回bool的一种委托,所以有异曲同工之妙
2. 事件处理
在事件处理程序中,lambda 表达式
可以用作处理事件的匿名方法。
示例:
button.Click += (sender, e) =>
{
Console.WriteLine("Button clicked!");
};
这个例子中,当按钮被点击时,lambda 表达式
定义的匿名方法将被调用。
3. 线程与任务
在多线程编程中,可以使用 lambda 表达式
来简化线程或任务的定义。
示例:
Task.Run(() =>
{
// 任务代码
Console.WriteLine("Task running");
});
4. LINQ 查询表达式
除了常见的 Any
、Where
方法外,lambda 表达式
还可以用于 Select
、OrderBy
、GroupBy
等 LINQ 方法。
示例:
var names = items.Select(item => item.name).ToList();
在这个例子中,lambda 表达式
用于从 items
列表中选择 name
属性,并生成一个新列表。
5. 简化条件逻辑
你可以在任何需要传递函数或执行代码块的地方使用 lambda 表达式
,比如简化条件逻辑。
示例:
Action action = () => Console.WriteLine("Hello, World!");
action();
这个例子中,Action
是一个不接受参数且不返回值的委托类型。lambda 表达式
定义了 action
的行为,并在后续调用时执行。
6. 自定义扩展方法
你可以使用 lambda 表达式
在自定义扩展方法中作为参数传递,来灵活地定义行为。
示例:
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (T element in source)
{
action(element);
}
}
// 使用扩展方法
items.ForEach(item => Console.WriteLine(item.name));
委托中的闭包提供了强大的功能,使得你能够创建灵活的、具有状态的匿名函数。
info => info.id == 1001
为什么要写这么奇怪的语法?
这是因为 lambda 表达式
的语法和功能所决定的。Lambda 表达式
是 C# 中用于简化匿名方法的一种语法,它可以直接在代码中定义一个小型函数,并将其传递给 LINQ 方法(如 Any
、FirstOrDefault
、Where
等)。这些方法依赖于 lambda 表达式
来描述要对集合中的每个元素执行的操作。
为什么可以把 item
当参数来写?
Lambda 表达式
的作用就是为每个元素定义一个临时函数。这个临时函数接受集合中的一个元素作为输入参数,并对其进行操作。因此,当你写 item => item.id == 1001
时,item
就是这个临时函数的参数,表示集合中的每一个单独的元素。
为什么知道它就是个体?
这是 LINQ
方法的设计原理决定的。比如,Any
方法定义为接受一个返回布尔值的函数(也就是 Predicate
),它会自动将集合中的每个元素依次传递给这个函数,并检查这个元素是否满足给定的条件。因此,在 Any
方法的上下文中,lambda 表达式
的参数必然就是集合的一个元素。
为什么要用这种语法?
Lambda 表达式
是为了简化代码而设计的。相比传统的匿名方法,它更加简洁,且与 LINQ 查询表达式紧密结合,使得对集合进行查询、过滤、投影等操作变得更加直观和方便。
传统匿名方法的写法:
在 lambda 表达式
之前,如果要实现相同的功能,可能会用如下的代码:
bool exists = items.Any(delegate(iteminfo item) {
return item.id == 1001;
});
使用 lambda 表达式
后,代码简化为:
bool exists = items.Any(item => item.id == 1001);
这个语法更简洁,更易读,也减少了代码的冗余。
Lambda 表达式的结构:
Lambda 表达式
的语法是 (参数) => { 表达式或代码块 }
,其中:
参数
是传递给匿名函数的输入参数,在 LINQ 中通常是集合的单个元素。=>
是lambda
运算符,表示从参数到函数体的映射。{ 表达式或代码块 }
是函数体,可以是单个表达式(返回值)或一个代码块。
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 使用 Lambda 表达式查找偶数
bool hasEven = numbers.Any(n => n % 2 == 0);
// Lambda 表达式中的 `n` 是 List<int> 的每个元素,类似于传统循环中的 `foreach (var n in numbers) { }`
在这个例子中,n => n % 2 == 0
是一个 lambda 表达式
,它接受集合中的每一个 int
元素 n
,并检查这个元素是否为偶数。
总结:
Lambda 表达式
是为了简化代码并与 LINQ 紧密结合的一种语法。- 表达式中的参数表示集合中的单个元素。
- 通过这种语法,你可以简洁地定义对集合每个元素的操作,从而更方便地对集合进行过滤、查询等操作。
为什么不用填上参数类型
在 C# 的 lambda 表达式
中,可以不用显式指定参数类型的原因,源于编译器的类型推断机制。
类型推断的原理
在 LINQ
方法(如 Any
、Where
、Select
等)的使用过程中,lambda 表达式
所操作的对象是某个集合中的元素,而这个集合的元素类型是明确的。因此,编译器能够自动推断出 lambda 表达式
参数的类型。
List<iteminfo> items = new List<iteminfo>();
// 使用 lambda 表达式查找 id 为 1001 的元素
bool exists = items.Any(item => item.id == 1001);
在这个例子中,items
是一个 List<iteminfo>
,编译器知道 List<iteminfo>
中的元素类型是 iteminfo
,所以在 item => item.id == 1001
中,item
的类型自动被推断为 iteminfo
。
类型推断的好处
- 代码简洁:你不必重复写类型信息,代码更加简洁、易读。
- 减少冗余:避免不必要的类型声明,可以使代码更加清晰,尤其在复杂的查询表达式中。
需要显式指定类型的情况
虽然通常编译器可以推断出参数类型,但在某些情况下,显式指定参数类型可能是必要的。例如:
- 当编译器无法推断类型时:如果类型推断不明确,编译器可能会要求你显式地指定类型。
- 使用复杂类型或匿名类型:有时候,显式声明类型可以让代码更清晰,尤其在涉及复杂的或不常见的类型时。
显式指定参数类型的方式
如果你愿意,也可以显式指定参数的类型。显式指定参数类型时,需要使用括号包裹参数:
bool exists = items.Any((iteminfo item) => item.id == 1001);
bool exists = items.Any((iteminfo item) => {return item.id == 1001;
});也可以
在这个例子中,iteminfo
是 item
的类型。这种写法与前面不显式指定类型的写法功能相同,只是显式地告诉编译器 item
的类型是 iteminfo
。
总结
- 类型推断:编译器可以通过
lambda 表达式
的上下文自动推断参数的类型,因此你通常不需要显式指定类型。 - 代码简化:类型推断机制让代码更简洁,减少了不必要的冗余。
- 灵活性:在需要时,你仍然可以显式地指定参数类型,以提高代码的可读性或解决推断问题。
查找 List<iteminfo>里是否存在某个值
1. 使用 Any
方法
Any
方法用于确定集合中是否存在满足指定条件的元素。如果存在则返回 true
,否则返回 false
。
bool exists = items.Any(item => item.id == 1001);
这里的item指的是List的items的单个个体
在 Any
、FirstOrDefault
、Where
等方法中的 lambda 表达式中,item => item.id == 1001
的 item
代表 List
中的每个单独的元素。
你可以给这个参数取任何名字,只要它能清晰地表达你的意图,并且在 lambda 表达式中使用一致即可。
2. 使用 FirstOrDefault
方法
FirstOrDefault
方法用于查找第一个满足条件的元素,如果不存在这样的元素,则返回 null
或类型的默认值。
iteminfo foundItem = items.FirstOrDefault(item => item.id == 1001);
你可以通过检查 foundItem
是否为 null
来判断是否找到了满足条件的元素。
3. 使用 Where
方法(如果需要获取所有匹配项)
如果可能有多个 id
为 1001
的项,并且你想获取所有这些项,可以使用 Where
方法。
List<iteminfo> matchingItems = items.Where(item => item.id == 1001).ToList();