[译]聊聊C#中的泛型的使用(新手勿入)
写在前面
今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发现了一些问题,因此也进行了纠正,当然,原文的地址我放在最下面,如果你的英文水平比较好的话,可以直接直接阅读全文。同时最近建了一个.NET Core实战项目交流群637326624,有兴趣的朋友可以来相互交流。目前.NET Core实战项目之CMS的教程也已经更新了6篇了,目前两到三天更新一篇。
介绍
C#和.NET中的泛型程序具有强类型集合的许多优点,并为代码提供更高质量和性能提升。泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework。类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大大简化类型之间的强制转换或装箱操作的过程(装箱、拆箱问题)。说白了,泛型就是通过参数化类型来实现在同一份代码上操作多种数据类型,利用“参数化类型”将类型抽象化,从而实现灵活的复用。每个集合的详细规范可以在System.Collection.Generic名称空间下找到。
装箱和拆箱
.Net定义了两种主要的数据类型来表示变量,也就是传说中的值类型和引用类型。这是需要装箱和拆箱的地方。装箱是一种通过将变量存储到System.Object中来显式地将值类型转换为引用类型的机制。当您装入值时,CLR会将新对象分配到堆中,并将值类型的值复制到该实例中。例如,您创建了一个int类型的变量:
int a = 20;
object b = a; //装箱
相反的操作是拆箱,它是将引用类型转换回值类型的过程。此过程验证接收数据类型是否与装箱类型一致;
int c = (int)b; // 拆箱
C#编译器可以看到从int到object的赋值,反之亦然。当编译该程序并通过IL解析器检查IL生成的代码时,您会注意到当b被赋值为a时,程序通过在IL中自动插入一个box指令来响应,当c被赋值为b时如下;
代码加载常量20并将其存储在本地插槽中;它将值20加载到堆栈中并将其装箱。最后,它将被装箱的20返回到堆栈上,并将其拆箱为int类型
这个过程.NET CLR执行了一系列操作,例如,首先在托管堆中分配一个对象,然后在装箱中将值转换为内存位置,并在拆箱期间将值存储在堆上并且必须转回到堆栈。因此,从性能的角度来看,装箱和拆箱过程在泛型中具有非常重要的意义,因为这个过程如果不使用泛型的话会耗费更多地资源。
泛型类
可以通过在类名后面加上符号来定义泛型类。这里没有强制必须将“T”字放在泛型的定义中。您可以在TestClass <>类声明中使用任何单词。
public class TestClass<T> { }
System.Collection.Generic命名空间下还定义了许多实现了这些关键字接口的类型。下表列出了此命名空间的核心类类型。
泛型类 | 描述 |
---|---|
Collection | 泛型集合的基类,可以比较两个泛型对象是否相等 |
Dictionary<TKey, TValue> | 键值对的泛型集合 |
List | 可动态调整列表项的大小 |
Queue | 先进先出(FIFO)列表的泛型实现 |
Stack | 后进先出(LIFO)列表的泛型实现 |
简单的泛型类示例
以下示例显示了一个简单的泛型类型的操作。TestClass 定义一个长度为5的泛型类型数组。Add()方法负责将任何类型的对象添加到集合中,而Indexer属性是循环语句迭代的实现。最后在主类中,我们使用整形类型来实例化TestClass 类,并使用Add()方法将一些整数类型数据添加到集合中。
using System;
using System.Collections.Generic;
namespace GenericApp
{
public class TestClass<T>
{
// 定义一个长度为5的泛型类型的数组
T[] obj = new T[5];
int count = 0;
// 向反省类型添加数据
public void Add(T item)
{
//checking length
if (count + 1 < 6)
{
obj[count] = item;
}
count++;
}
//foreach语句迭代索引
public T this[int index]
{
get { return obj[index]; }
set { obj[index] = value; }
}
}
class Program
{
static void Main(string[] args)
{
//用整形来实例化泛型类
TestClass<int> intObj = new TestClass<int>();
//向集合中添加int数据
intObj.Add(1);
intObj.Add(2);
intObj.Add(3); //没有装箱
intObj.Add(4);
intObj.Add(5);
//遍历显示数据
for (int i = 0; i < 5; i++)
{
Console.WriteLine(intObj[i]); //没有拆箱
}
Console.ReadKey();
}
}
}
在构建并运行该程序之后,程序的输出如下所示;
泛型的主要特性
泛型类型的一些重要特征使它们相比传统的非泛型类型具有如下的显著特征:
- 类型安全
- 性能
- 二进制代码复用
类型安全
泛型最重要的特征之一是类型安全性。对于非泛型ArrayList类,如果使用对象类型,则可以向集合中添加任何类型,这些类型有时会导致严重的问题。下面的示例显示向ArrayList类型的集合添加一个整数、字符串和对象;
ArrayList obj = new ArrayList();
obj.Add(50);
obj.Add("Dog");
obj.Add(new TestClass());
现在,如果使用整数对象来使用foreach语句进行遍历的话,当编译器接受到代码,但是因为集合中的所有元素都不是整数,所以会导致运行时异常;
foreach(int i in obj)
{
Console.WriteLine(i);
}
编程的经验法则是应该尽早检测到错误。对于泛型类Test,泛型类型T定义允许哪些类型。通过使用Test的定义,只能向集合添加整型类型的数据。这时候当Add()方法具有以下无效参数的时候编译器将不编译代码;
Test<int> obj = new Test<int>();
obj.Add(50);
obj.Add("Dog"); //编译错误
obj.Add(new TestClass()); //编译错误
性能
在下面的示例中,ArrayList类存储对象,并且定义了Add()方法来存储一些整型参数。因此,整数类型被装箱。当使用foreach语句读取ArrayList中的值时,将发生拆箱。
ArrayList obj = new ArrayList();
obj.Add(50); //装箱- 值类型转换成引用类型
int x= (int)obj[0]; //拆箱
foreach(int i in obj)
{
Console.WriteLine(i); // 拆箱
}
注意:泛型比其他集合(如ArrayList)更快。
代替使用对象类型,TestClass类的泛型类型被定义为int,因此在从编译器动态生成的类中将使用int类型。所以将不会发生装箱和拆箱,如下所示;
TestClass<int> obj = new TestClass<int>();
obj.Add(50); //没有装箱
int x= obj[0]; // 没有拆箱
foreach(int i in obj)
{
Console.WriteLine(i); //没有拆箱
}
二进制代码复用
泛型类型提供了一种源代码保护机制。泛型类可以定义一次,并且可以使用许多不同类型来进行实例化。泛型可以在一种CLR支持的语言中定义,并可以被另一种.NET语言使用。以下TestClass 使用int和string类型进行实例化:
TestClass<int> obj = new TestClass<int>();
obj.Add(50);
TestClass<string> obj1 = new TestClass<string>();
Obj1.Add("hello");
通用方法
虽然大多数开发人员通常会使用基类库中的现有泛型类型,但也有可能会构建自己的泛型成员和自定义的泛型类型。
本示例的目的是构建一个交换方法,该方法可以使用单个类型参数对任何可能的数据类型(基于值或基于引用)进行操作。由于交换算法的性质,传入的参数将作为使用ref关键字修饰的引用类型来进行发送。
using System;
using System.Collections.Generic;
namespace GenericApp
{
class Program
{
//泛型方法
static void Swap<T>(ref T a, ref T b)
{
T temp;
temp = a;
a = b;
b = temp;
}
static void Main(string[] args)
{
//交换两个整形数据
int a = 40, b = 60;
Console.WriteLine("Before swap: {0}, {1}", a, b);
Swap<int>(ref a, ref b);
Console.WriteLine("After swap: {0}, {1}", a, b);
Console.ReadLine();
}
}
}
编译此泛型方法实现的程序后,输出如下所示;
字典
字典也被称为映射或散列表。它表示允许您基于关键字来访问元素的数据结构。字典的一个重要特征是更快的查找; 您可以添加或删除选项而不会产生性能开销。
.Net提供了几个字典类,例如Dictionary <TKey,TValue>。类型参数TKey和TValue分别表示关键字的类型和它可以存储的值。
简单的字典示例
以下示例演示使用泛型的简单字典集合。在此程序中,将创建一个Dictionary类型对象,该对象接受int作为键,字符串作为值。然后我们将一些字符串值添加到字典集合中,最后显示字典集合元素。
using System;
using System.Collections.Generic;
namespace GenericApp
{
public class Program
{
static void Main(string[] args)
{
//定义一个字典集合
Dictionary<int,string> dObj = new Dictionary<int,string>(5);
//向字典中添加类型
dObj.Add(1,1,"Tom");
dObj.Add(2,"John");
dObj.Add(3, "Maria");
dObj.Add(4, "Max");
dObj.Add(5, "Ram");
//输出数据
for (int i = 1; i <= dObj.Count;i++)
{
Console.WriteLine(dObj[i]);
}
Console.ReadKey();
}
}
}
以下示例通过定义附加类emp来描述一些更复杂的问题,其中我们覆盖ToString()方法以显示特定员工的名称和薪水。稍后在Main()方法中,创建一个新的Dictionary <TKey,TValue)的实例,其中键的类型为string,值为emp类型。构造函数分配2个元素的容量。emp对象和作为键的字符串值被添加到字典集合中。最后,使用foreach语句迭代集合元素并显示在屏幕上。
using System;
using System.Text;
using System.Collections.Generic;
namespace GenericApp
{
public class emp
{
private string name;
private int salary;
public emp(string name,int salary)
{
this.name = name;
this.salary = salary;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder(200);
sb.AppendFormat("{0},{1}",name,salary);
return sb.ToString();
}
}
public class Program
{
static void Main(string[] args)
{
//定义一个字典集合
Dictionary<string, emp> dObj = new Dictionary<string, emp>(2);
//向字典中添加元素
emp tom = new emp("tom", 2000);
dObj.Add("tom",tom); // 键,值
emp john = new emp("john", 4000);
dObj.Add("john",john);
//print data
foreach(Object str in dObj.Values)
{
Console.WriteLine(str);
}
Console.ReadKey();
}
}
}
队列
队列是一种特殊类型的容器,可确保以FIFO(先进先出)方式访问元素。队列集合最适合实现消息传递的组件。我们可以使用以下语法定义Queue集合对象:
Queue qObj = new Queue();
Queue集合的属性,方法和其他规则定义都位于Sysyem.Collection命名空间下。下表定义了关键成员;
System.Collection.Queue成员 | 定义 |
---|---|
Enqueue() | 将对象添加到队列的末尾。 |
Dequeue() | 从队列的开头删除对象。 |
Peek() | 返回队列开头的对象而不删除它。 |
下面演示了一个基本的队列类型的集合,将一些字符串类型值添加到集合中,最后使用while语句来显示整个集合中的数据 。
using System;
using System.Collections;
namespace GenericApp
{
class Program
{
static void Main(string[] args)
{
//定义一个队列
Queue qObj = new Queue();
//向队列中添加字符串数据
qObj.Enqueue("Tom");
qObj.Enqueue("Harry");
qObj.Enqueue("Maria");
qObj.Enqueue("john");
//显示队列中的数据
while(qObj.Count !=0 )
{
Console.WriteLine(qObj.Dequeue());
}
Console.ReadKey();
}
}
}
堆栈
Stack集合是LIFO的抽象(后进先出)。我们可以使用以下语法定义Stack集合对象:
Stack qObj = new Stack();
下表说明了堆栈的关键成员;
System.Collection.Stack成员 | 定义 |
---|---|
Contains() | 如果在集合中找到特定元素,则返回true。 |
Clear() | 删除集合的所有元素。 |
Peek() | 预览堆栈中的最新元素。 |
Push() | 它将元素推入堆栈。 |
Pop() | 返回并删除堆栈的顶部元素。 |
以下演示了堆栈集合。首先,将数组类型对象引用到堆栈集合中。然后使用Pop()方法从堆栈中删除集合中元素的值并显示在屏幕上。
using System;
using System.Collections;
namespace GenericApp
{
class Program
{
static void Main(string[] args)
{
int[] iArray = new int[] {1,2,3,4,5,6,7,8,9,10 };
//定义一个堆栈
Stack sObj = new Stack(iArray);
Console.WriteLine("Total items="+sObj.Count);
//显示集合数据
for (int i = 0; i < sObj.Count;++i )
{
Console.WriteLine(sObj.Pop());
}
Console.ReadKey();
}
}
}
在使用泛型实现的另一个示例中,使用Push()方法将5个项添加到堆栈中。然后使用循环迭代输出堆栈中的数据。堆栈的枚举器不会删除数据; 它只是以LIFO方式返回每个项目,如下所示:
using System;
using System.Collections;
namespace GenericApp
{
class Program
{
static void Main(string[] args)
{
//定义一个堆栈
Stack sObj = new Stack();
//向集合添加数据
for (int i = 0; i < 5; ++i)
{
sObj.Push(i+1);
}
Console.WriteLine("Total items=" + sObj.Count);
//打印数据
foreach (int i in sObj)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
}
}
总结
今天忙里偷闲,在浏览外文的时候看到一篇讲泛型的文章,因此就加上自己的理解进行了相关翻译,也加深了自己对泛型的理解!如果英文比较好的话可以直接访问https://www.c-sharpcorner.com/UploadFile/84c85b/using-generics-with-C-Sharp/ 自行查看!当然,我在翻译的过程中也发现了文中的一些错误,所以进行了更正!同时最近建了一个.NET Core实战项目交流群637326624,有兴趣的朋友可以来相互交流。目前.NET Core实战项目之CMS的教程也已经更新了6篇了,目前两到三天更新一篇。最后感谢大家的阅读。
Seaching TreeVIew WPF
项目中有一个树形结构的资源,需要支持搜索功能,搜索出来的结果还是需要按照树形结构展示,下面是简单实现的demo。
1.首先创建TreeViewItem的ViewModel,一般情况下,树形结构都包含DisplayName,Deepth,Parent,Children,Id, IndexCode,Visibility等属性,具体代码如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Collections.ObjectModel; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Windows; 8 9 namespace TreeViewDemo 10 { 11 public class TreeViewItemVM : NotifyPropertyChangedBase 12 { 13 public TreeViewItemVM () 14 { 15 Visible = Visibility.Visible; 16 } 17 18 private TreeViewItemVM parent; 19 public TreeViewItemVM Parent 20 { 21 get 22 { 23 return this.parent; 24 } 25 set 26 { 27 if (this.parent != value) 28 { 29 this.parent = value; 30 this.OnPropertyChanged(() => this.Parent); 31 } 32 } 33 } 34 35 private ObservableCollection<TreeViewItemVM> children; 36 public ObservableCollection<TreeViewItemVM> Children 37 { 38 get 39 { 40 return this.children; 41 } 42 set 43 { 44 if (this.children != value) 45 { 46 this.children = value; 47 this.OnPropertyChanged(() => this.Children); 48 } 49 } 50 } 51 52 private string id; 53 public string ID 54 { 55 get 56 { 57 return this.id; 58 } 59 set 60 { 61 if (this.id != value) 62 { 63 this.id = value; 64 this.OnPropertyChanged(() => this.ID); 65 } 66 } 67 } 68 69 private string indexCode; 70 public string IndexCode 71 { 72 get { return indexCode; } 73 set 74 { 75 if (indexCode != value) 76 { 77 indexCode = value; 78 this.OnPropertyChanged(() => IndexCode); 79 } 80 } 81 } 82 83 private string displayName; 84 public string DisplayName 85 { 86 get 87 { 88 return this.displayName; 89 } 90 set 91 { 92 if (this.displayName != value) 93 { 94 this.displayName = value; 95 this.OnPropertyChanged(() => this.DisplayName); 96 } 97 } 98 } 99 100 private int deepth; 101 public int Deepth 102 { 103 get 104 { 105 return this.deepth; 106 } 107 set 108 { 109 if (this.deepth != value) 110 { 111 this.deepth = value; 112 this.OnPropertyChanged(() => this.Deepth); 113 } 114 } 115 } 116 117 private bool hasChildren; 118 public bool HasChildren 119 { 120 get 121 { 122 return this.hasChildren; 123 } 124 set 125 { 126 if (this.hasChildren != value) 127 { 128 this.hasChildren = value; 129 this.OnPropertyChanged(() => this.HasChildren); 130 } 131 } 132 } 133 134 private NodeType type; 135 public NodeType Type 136 { 137 get { return type; } 138 set 139 { 140 if (type != value) 141 { 142 type = value; 143 OnPropertyChanged(() => this.Type); 144 } 145 } 146 } 147 148 private Visibility visible; 149 public Visibility Visible 150 { 151 get { return visible; } 152 set 153 { 154 if (visible != value) 155 { 156 visible = value; 157 OnPropertyChanged(() => this.Visible); 158 } 159 } 160 } 161 162 public bool NameContains(string filter) 163 { 164 if (string.IsNullOrWhiteSpace(filter)) 165 { 166 return true; 167 } 168 169 return DisplayName.ToLowerInvariant().Contains(filter.ToLowerInvariant()); 170 } 171 } 172 }
2.创建TreeViewViewModel,其中定义了用于过滤的属性Filter,以及过滤函数,并在构造函数中初始化一些测试数据,具体代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Collections.ObjectModel; 4 using System.ComponentModel; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 using System.Windows.Data; 9 10 namespace TreeViewDemo 11 { 12 public class TreeViewViewModel : NotifyPropertyChangedBase 13 { 14 public static TreeViewViewModel Instance = new TreeViewViewModel(); 15 16 private TreeViewViewModel() 17 { 18 Filter = string.Empty; 19 20 Root = new TreeViewItemVM() 21 { 22 Deepth = 0, 23 DisplayName = "五号线", 24 HasChildren = true, 25 Type = NodeType.Unit, 26 ID = "0", 27 Children = new ObservableCollection<TreeViewItemVM>() { 28 new TreeViewItemVM() { DisplayName = "站台", Deepth = 1, HasChildren = true, ID = "1", Type = NodeType.Region, 29 Children = new ObservableCollection<TreeViewItemVM>(){ 30 new TreeViewItemVM() { DisplayName = "Camera 01", Deepth = 2, HasChildren = false, ID = "3",Type = NodeType.Camera }, 31 new TreeViewItemVM() { DisplayName = "Camera 02", Deepth = 2, HasChildren = false, ID = "4",Type = NodeType.Camera }, 32 new TreeViewItemVM() { DisplayName = "Camera 03", Deepth = 2, HasChildren = false, ID = "5",Type = NodeType.Camera }, 33 new TreeViewItemVM() { DisplayName = "Camera 04", Deepth = 2, HasChildren = false, ID = "6",Type = NodeType.Camera }, 34 new TreeViewItemVM() { DisplayName = "Camera 05", Deepth = 2, HasChildren = false, ID = "7", Type = NodeType.Camera}, 35 }}, 36 new TreeViewItemVM() { DisplayName = "进出口", Deepth = 1, HasChildren = true, ID = "10", Type = NodeType.Region, 37 Children = new ObservableCollection<TreeViewItemVM>(){ 38 new TreeViewItemVM() { DisplayName = "Camera 11", Deepth = 2, HasChildren = false, ID = "13",Type = NodeType.Camera }, 39 new TreeViewItemVM() { DisplayName = "Camera 12", Deepth = 2, HasChildren = false, ID = "14",Type = NodeType.Camera }, 40 new TreeViewItemVM() { DisplayName = "Camera 13", Deepth = 2, HasChildren = false, ID = "15",Type = NodeType.Camera }, 41 new TreeViewItemVM() { DisplayName = "Camera 14", Deepth = 2, HasChildren = false, ID = "16", Type = NodeType.Camera}, 42 new TreeViewItemVM() { DisplayName = "Camera 15", Deepth = 2, HasChildren = false, ID = "17", Type = NodeType.Camera}, 43 }}, 44 } 45 }; 46 47 InitTreeView(); 48 } 49 50 private ObservableCollection<TreeViewItemVM> selectedCameras = new ObservableCollection<TreeViewItemVM>(); 51 52 private TreeViewItemVM root; 53 public TreeViewItemVM Root 54 { 55 get 56 { 57 return this.root; 58 } 59 set 60 { 61 if (this.root != value) 62 { 63 this.root = value; 64 this.OnPropertyChanged(() => this.Root); 65 } 66 } 67 } 68 69 /// <summary> 70 /// 过滤字段 71 /// </summary> 72 private string filter; 73 public string Filter 74 { 75 get 76 { 77 return this.filter; 78 } 79 set 80 { 81 if (this.filter != value) 82 { 83 84 this.filter = value; 85 this.OnPropertyChanged(() => this.Filter); 86 87 this.Refresh(); 88 } 89 } 90 } 91 92 /// <summary> 93 /// View 94 /// </summary> 95 protected ICollectionView view; 96 public ICollectionView View 97 { 98 get 99 { 100 return this.view; 101 } 102 set 103 { 104 if (this.view != value) 105 { 106 this.view = value; 107 this.OnPropertyChanged(() => this.View); 108 } 109 } 110 } 111 112 /// <summary> 113 /// 刷新View 114 /// </summary> 115 public void Refresh() 116 { 117 if (this.View != null) 118 { 119 this.View.Refresh(); 120 } 121 } 122 123 private bool DoFilter(Object obj) 124 { 125 TreeViewItemVM item = obj as TreeViewItemVM; 126 if (item == null) 127 { 128 return true; 129 } 130 131 bool result = false; 132 foreach (var node in item.Children) 133 { 134 result = TreeItemDoFilter(node) || result; 135 } 136 137 return result || item.NameContains(this.Filter); 138 } 139 140 private bool TreeItemDoFilter(TreeViewItemVM vm) 141 { 142 if (vm == null) 143 { 144 return true; 145 } 146 147 bool result = false; 148 if (vm.Type == NodeType.Region || vm.Type == NodeType.Unit) 149 { 150 foreach (var item in vm.Children) 151 { 152 result = TreeItemDoFilter(item) || result; 153 } 154 } 155 156 if (result || vm.NameContains(this.Filter)) 157 { 158 result = true; 159 vm.Visible = System.Windows.Visibility.Visible; 160 } 161 else 162 { 163 vm.Visible = System.Windows.Visibility.Collapsed; 164 } 165 166 return result; 167 } 168 169 public void InitTreeView() 170 { 171 this.View = CollectionViewSource.GetDefaultView(this.Root.Children); 172 this.View.Filter = this.DoFilter; 173 this.Refresh(); 174 } 175 } 176 }
3.在界面添加一个TreeView,并添加一个简单的Style,将ViewModel中必要数据进行绑定:
1 <Window x:Class="TreeViewDemo.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="MainWindow" Height="450" Width="525"> 5 <Window.Resources> 6 <Style x:Key="style" TargetType="{x:Type TreeViewItem}"> 7 <Setter Property="Template"> 8 <Setter.Value> 9 <ControlTemplate TargetType="{x:Type TreeViewItem}"> 10 <Grid Visibility="{Binding Visible}" Background="{Binding Background}"> 11 <ContentPresenter ContentSource="Header"/> 12 </Grid> 13 14 <ControlTemplate.Triggers> 15 <Trigger Property="IsSelected" Value="true"> 16 <Setter Property="Background" Value="Green"/> 17 </Trigger> 18 </ControlTemplate.Triggers> 19 </ControlTemplate> 20 </Setter.Value> 21 </Setter> 22 </Style> 23 </Window.Resources> 24 <Grid> 25 <Grid.RowDefinitions> 26 <RowDefinition Height="Auto"/> 27 <RowDefinition Height="*"/> 28 </Grid.RowDefinitions> 29 30 <TextBox x:Name="searchTxt" Width="200" HorizontalAlignment="Center" Height="40" 31 Margin="20" Text="{Binding Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> 32 33 <TreeView 34 Grid.Row="1" 35 ItemsSource="{Binding View}"> 36 <TreeView.ItemTemplate> 37 <HierarchicalDataTemplate ItemContainerStyle ="{StaticResource style}" ItemsSource="{Binding Children}"> 38 <Grid Height="25" > 39 <TextBlock 40 x:Name="txt" 41 VerticalAlignment="Center" 42 Text="{Binding DisplayName}" 43 TextTrimming="CharacterEllipsis" 44 ToolTip="{Binding DisplayName}" /> 45 </Grid> 46 </HierarchicalDataTemplate> 47 </TreeView.ItemTemplate> 48 </TreeView> 49 </Grid> 50 </Window>
4.在给界面绑定具体的数据
1 using System.Windows; 2 3 namespace TreeViewDemo 4 { 5 /// <summary> 6 /// MainWindow.xaml 的交互逻辑 7 /// </summary> 8 public partial class MainWindow : Window 9 { 10 public MainWindow() 11 { 12 InitializeComponent(); 13 this.Loaded += MainWindow_Loaded; 14 } 15 16 void MainWindow_Loaded(object sender, RoutedEventArgs e) 17 { 18 this.DataContext = TreeViewViewModel.Instance; 19 } 20 } 21 }
5.运行结果:
可编辑树Ztree的使用(包括对后台数据库的增删改查)
找了很多网上关于Ztree的例子和代码才搞定。
首先,关于Ztree的代码不介绍了,网上下载之后,引用下列四个文件就能使用了。
1.关于配置选项。主要通过回调函数来实现向后台发送数据,实现增删改查。
1 var setting = { 2 view: { 3 addHoverDom: addHoverDom,//显示操作图标 4 removeHoverDom: removeHoverDom,//移除图标 5 selectedMulti: false 6 }, 7 check: { 8 enable: true 9 }, 10 data: { 11 simpleData: { 12 enable: true 13 } 14 }, 15 callback: { 16 onRename: zTreeOnRename,//修改 17 onRemove: zTreeOnRemove,//删除 18 onClick: zTreeOnClickRight, 19 20 }, 21 edit: { 22 enable: true, 23 showRemoveBtn: true, 24 showRenameBtn: true, 25 removeTitle: "删除", 26 renameTitle: "修改" 27 } 28 };
$(document).ready(function () { $.ajax({ url: "GetZtreeJson", data: { ProjectId: "@ViewBag.ProjectId" }, type: "post", dataType: "json", success: function (data) { $.fn.zTree.init($("#test"), setting, data);//实现加载树的方法 } }) $("#btnReturn").click(function () { window.parent.frameReturnByClose(); }); //$.fn.zTree.init($("#treeDemo"), setting, zNodes); }); var newCount = 1; function addHoverDom(treeId, treeNode) { var sObj = $("#" + treeNode.tId + "_span"); if (treeNode.editNameFlag || $("#addBtn_" + treeNode.tId).length > 0) return; var addStr = "<span class='button add' id='addBtn_" + treeNode.tId + "' title='add node' onfocus='this.blur();'></span>"; sObj.after(addStr); var btn = $("#addBtn_" + treeNode.tId); if (btn) btn.bind("click", function () { var zTree = $.fn.zTree.getZTreeObj("test"); //增加节点的方法 $.ajax({ url: "AddNode", data: { ParentId: treeNode.id }, type: "post", success: function (data) { if (data.message == "success") { //此方法是js在前段增加节点方法,一定要通过后台返回的id来增加,不然新增的节点会出现节点id为null的问题 zTree.addNodes(treeNode, { id: data.id, ParentId: treeNode.id, name: "new node" + (newCount++) }); } else { $.messageBox5s('@Resource.Tip', "新增节点失败!联系管理员!"); } } }) return false; }); };//删除节点 function zTreeOnRemove(event, treeId, treeNode) { { $.ajax({ url: "DeleteNode", type: "post", data: { NodeId: treeNode.id }, success: function (data) { if (data == "success") { } else { $.messageBox5s('@Resource.Tip', "删除节点失败!联系管理员!"); } } }) } } function removeHoverDom(treeId, treeNode) { $("#addBtn_" + treeNode.tId).unbind().remove(); }; //修改节点 function zTreeOnRename(event, treeId, treeNode) { $.ajax({ url: "EditNode", type: "post", data: { NodeId: $.trim(treeNode.id), name: treeNode.name }, success: function (data) { if (data != "success") { $.messageBox5s('@Resource.Tip', "修改节点失败!联系管理员!"); } } }) }; // 树的单击事件 function zTreeOnClickRight(event, treeId, treeNode) { var treeid = treeNode.id; $("#hidId").val(treeid); $("#ifm").attr("src", "FileView?NodeId=" + treeid); } function treeShow(data) { zTreeObj = $.fn.zTree.init($("#test"), setting, data); zTreeObj.expandAll(true); }
2.后台处理的方法。。我项目中是使用C#代码写的,mvc框架
1 [Description("项目资料-树形")] 2 [KeyCode(Name = "Tree")] 3 [IsModule] 4 [SupportFilter(ActionName = "Tree")] 5 public ActionResult TreeIndex(Guid ProjectId) 6 { 7 8 ViewBag.ProjectId = ProjectId; 9 var ProjectCode = IProjectContract.GetProjectInputById(ProjectId).ProjectCode; 10 string count = ProjectResourceContract.CountNumber(ProjectCode); 11 ViewBag.Count = count; 12 return View(); 13 } 14 15 public JsonResult GetZtreeJson(Guid ProjectId) 16 { 17 var list = ProjectResourceContract.GetNodeJsonByProject(ProjectId); 18 19 20 return Json((from p in list 21 select new 22 { 23 id = p.Id, 24 pId = p.pid, 25 open = "true", 26 name = p.name 27 }).ToList()); 28 } 29 public JsonResult AddNode(string ParentId) 30 { 31 int sort = ProjectResourceContract.GetLastNodeSortFormParent(ParentId); 32 //string nodeid = ProjectResourceContract.GetCurrentNewNodeId(ParentId); 33 if (sort == 1) 34 { 35 var node = ProjectResourceContract.GetNodeByNodeId(ParentId); 36 node.type = "1"; 37 ProjectResourceContract.EditNode(ParentId, node.type); 38 } 39 Guid nodeId = Guid.NewGuid(); 40 var ProjectCode = ProjectResourceContract.GetNodeByNodeId(ParentId).ProjectCode; 41 var result = ProjectResourceContract.AddNode(new TB_Project_Nodes() 42 { 43 Id = nodeId, 44 name = "New Node" + sort, 45 type = "2", 46 pid = ParentId, 47 sort = sort, 48 state = "true", 49 ProjectCode = ProjectCode, 50 }); 51 if (result.Successed) 52 { 53 return Json(new { message = "success", id = nodeId }); 54 } 55 else 56 { 57 return Json("error"); 58 } 59 } 60 public JsonResult DeleteNode(string NodeId) 61 { 62 var result = ProjectResourceContract.DeleteNode(NodeId); 63 if (result.Successed) 64 { 65 return Json("success"); 66 } 67 else 68 { 69 return Json("error"); 70 } 71 } 72 public JsonResult EditNode(string NodeId, string name, string path = "", string ProjectCode = "") 73 { 74 OperationResult result; 75 if (!string.IsNullOrEmpty(path)) 76 { 77 path = path.TrimEnd('+'); 78 79 path = "UpDir/" + ProjectCode + "/施工资料/" + path; 80 result = ProjectResourceContract.EditNode(NodeId, name, path); 81 } 82 else 83 { 84 result = ProjectResourceContract.EditNode(NodeId, name, ""); 85 } 86 if (result.Successed) 87 { 88 return Json("success"); 89 } 90 else 91 { 92 return Json("error"); 93 } 94 }
我项目中的情况是需要用ztree来实现创建目录,然后上传施工资料的方法。所以,projectid字段,大家不需要在意。是项目的id
3.给大家看一下我的数据库字段设计,附上关于增删改查操作数据库的代码。
这里*节点的pid为0,其次projectcode是我项目中使用到的,可以不用考虑。state本来是用于ztree中open配置信息用的。默认展开节点的配置。
type是我用于判断此节点是否包是文件节点用的。(包含子节点的为文件夹节点,没有子节点的就是文件节点)
4.serveices代码
1 //获取树所有节点显示 2 public List<TB_Project_Nodes> GetNodeJsonByProject(Guid ProjectId) 3 { 4 5 6 var project = ProjectsRepository.GetByKey(ProjectId); 7 string TopNode = project.ProjectCode; 8 List<TB_Project_Nodes> ALLLIST = NodesRepository.Entities.Where(t => t.ProjectCode == TopNode).ToList(); 9 //var top = NodesRepository.Entities.FirstOrDefault(t => t.Id.ToString() == TopNode); 10 TB_Project_Nodes top = ALLLIST.FirstOrDefault(t => t.ProjectCode == TopNode&&t.pid=="0"); 11 if (top == null)//第一次创建 12 { 13 TB_Project_Nodes pn = new TB_Project_Nodes() { ProjectCode = TopNode, Id = Guid.NewGuid(), type = "1", pid = "0", sort = 1, name = project.ProjectName, state = "true" }; 14 NodesRepository.Insert(pn); 15 return new List<TB_Project_Nodes>() { pn }; 16 } 17 else//存在*节点 18 { 19 20 //List<TB_Project_Nodes> nodes = NodesRepository.Entities.Where(t => t.pid == TopNode).OrderBy(t => t.sort).ToList();//*节点下面的一级节点 21 List<TB_Project_Nodes> nodes = ALLLIST.Where(t => t.pid == top.Id.ToString()).OrderBy(t => t.sort).ToList();//*节点下面的一级节点 22 23 if (nodes.Count <= 0)//没有子节点 24 { 25 return new List<TB_Project_Nodes>() { top }; 26 } 27 else//存在子节点,遍历所有的子节点 28 { 29 30 List<TB_Project_Nodes> LIST = new List<TB_Project_Nodes>(); 31 LIST.Add(top); 32 LIST.AddRange(nodes); //添加一级节点 33 34 LIST = Test(nodes, LIST, ALLLIST); 35 36 return LIST; 37 } 38 39 } 40 41 } 42 //递归函数---把所有二级节点以及以下所有节点获取 43 public List<TB_Project_Nodes> Test(List<TB_Project_Nodes> list, List<TB_Project_Nodes> LIST, List<TB_Project_Nodes> ALLLIST) 44 { 45 List<TB_Project_Nodes> NEWLIST = new List<TB_Project_Nodes>(); 46 foreach (var item2 in list) 47 { 48 var secondNodes = ALLLIST.Where(t => t.pid == item2.Id.ToString()).OrderBy(t => t.sort).ToList(); 49 if (secondNodes.Count > 0) 50 { 51 52 NEWLIST.AddRange(secondNodes); 53 } 54 } 55 LIST.AddRange(NEWLIST); 56 //已经添加完本级节点 57 //添加下一级节点 58 if (NEWLIST.Count > 0) 59 { 60 Test(NEWLIST, LIST, ALLLIST); 61 } 62 return LIST; 63 } 64 //增加节点信息 65 public OperationResult AddNode(TB_Project_Nodes node) 66 { 67 68 int result = NodesRepository.Insert(node); 69 if (result == 0) 70 { 71 return new OperationResult(OperationResultType.Error, "error"); 72 } 73 else 74 { 75 return new OperationResult(OperationResultType.Success, "success"); 76 } 77 78 } 79 /// <summary> 80 /// 修改节点类型 81 /// </summary> 82 /// <param name="NodeId"></param> 83 /// <param name="type"></param> 84 /// <returns></returns> 85 public OperationResult EditNode(string NodeId, string type) 86 { 87 var node = NodesRepository.Entities.FirstOrDefault(t => t.Id.ToString() == NodeId); 88 node.type = type; 89 int result = NodesRepository.Update(node); 90 if (result == 0) 91 { 92 return new OperationResult(OperationResultType.Error, "error"); 93 } 94 else 95 { 96 return new OperationResult(OperationResultType.Success, "success"); 97 } 98 } 99 100 /// <summary> 101 /// 获取父级节点下面最大的一个排序+1 102 /// </summary> 103 /// <param name="ParentId"></param> 104 /// <returns></returns> 105 106 public int GetLastNodeSortFormParent(string ParentId) 107 { 108 var list = NodesRepository.Entities.Where(t => t.pid == ParentId).OrderByDescending(t => t.sort).ToList(); 109 if (list.Count <= 0) 110 { 111 return 1; 112 } 113 else 114 { 115 return list[0].sort + 1; 116 } 117 } 118 119 /// <summary> 120 /// 删除此节点时候,要把下面所有子节点删除 121 /// </summary> 122 /// <param name="NodeId"></param> 123 /// <returns></returns> 124 public OperationResult DeleteNode(string NodeId) 125 { 126 List<TB_Project_Nodes> ALLLIST = NodesRepository.Entities.ToList(); 127 // var node = NodesRepository.Entities.FirstOrDefault(t => t.Id.ToString() == NodeId); 128 var node = ALLLIST.FirstOrDefault(T => T.Id.ToString() == NodeId); 129 //获取下面的所有子节点 130 List<TB_Project_Nodes> LIST = new List<TB_Project_Nodes>(); 131 LIST.Add(node); 132 var list = ALLLIST.Where(t => t.pid == NodeId).ToList(); 133 if (list.Count > 0) 134 { 135 LIST.AddRange(list); 136 LIST = Test(list, LIST, ALLLIST); 137 } 138 for (int i = LIST.Count - 1; i >= 0; i--) 139 { 140 try 141 { 142 int result = NodesRepository.Delete(LIST[i].Id); 143 } 144 catch (Exception ex) 145 { 146 return new OperationResult(OperationResultType.Error, "error"); 147 throw ex; 148 } 149 150 } 151 152 return new OperationResult(OperationResultType.Success, "success"); 153 154 }
今天写一个wpf的demo,用到绑定数据,给控件绑定了数据源,但是数据却没有显示出来,排查代码发现绑定数据源的的成员用的是字段不是属性。
前端代码:
<Grid> <StackPanel Grid.Row="2" Margin="10"> <ListBox x:Name="listBox" Height="100"> </ListBox> </StackPanel> </Grid>
后台代码:
public Window3() { InitializeComponent(); List<Employe> list = new List<Employe>() { new Employe() { name="jack",age=18}, new Employe() { name="bob",age=20}, new Employe() { name="alice",age=21} }; listBox.ItemsSource = list; listBox.DisplayMemberPath = "name"; listBox.SelectedValuePath = "age"; }
//实体 public class Employe { public string name { get; set; } public int age { get; set; } }
如果把Employe的name,去掉{get;set;},改为一个字段, public string name;数据就无法绑定了。原因是属性的访问是由访问器完成的,因而属性可以进行数据绑定。
网上的文章有很多,但是好些没说到重点,基本都是说属性可以保护数据安全云云之类,整理了一下,有一下几个区别:
相同点:
都是类的成员,属性是类的属性,而字段是类的数据成员
不同点:
1 属性可进行数据绑定
2 属性可通过set和get方法进行数据安全性检验,而字段不行
3 属性可进行线程同步
public string Name
{
set{
lock(this)
{
}
}
}
4 属性可以是抽象的,而字段不行
5 属性可以接口的形式表现
6 基于属性的索引
7 不要直接把字段转化为属性
MSDN:
属性与字段
属性与字段都可在对象中存储和检索信息。它们的相似性使得在给定情况下很难确定哪个是更好的编程选择。
在以下情况下使用属性过程:
1. 需要控制设置或检索值的时间和方式时。
2. 属性有定义完善的一组值需要进行验证时。
3. 设置值导致对象的状态发生某些明显的变化(如 IsVisible 属性)。
4. 设置属性会导致更改其他内部变量或其他属性的值时。
5.必须先执行一组步骤,然后才能设置或检索属性时。
在以下情况下使用字段:
1. 值为自验证类型时。例如,如果将 True 或 False 以外的值赋给 Boolean 变量,就会发生错误或自动数据转换。
2. 在数据类型所支持范围内的任何值均有效时。Single 或 Double 类型的很多属性属于这种情况。
3. 属性是 String 数据类型,且对于字符串的大小或值没有任何约束时
C# 遍历Dictionary并修改其中的Value
C#的Dictionary类型的值,知道key后,value可以修改吗?答案是肯定能修改的。我在遍历的过程中可以修改Value吗?答案是也是肯定能修改的,但是不能用For each循环。否则会报以下的Exception.
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
之所以会报Exception是For each本身的问题,和Dictionary没关系。For each循环不能改变集合中各项的值,如果需要迭代并改变集合项中的值,请用For循环。
大家来看下例子:
1 // defined the Dictionary variable 2 Dictionary<int, string> td = new Dictionary<int, string>(); 3 td.Add(1, "str1"); 4 td.Add(2, "str2"); 5 td.Add(3, "str3"); 6 td.Add(4, "str4"); 7 // test for 8 TestForDictionary(td); 9 // test for each 10 TestForEachDictionary(td);
TestForDictionary Code
1 static void TestForDictionary(Dictionary<int, string> paramTd) 2 { 3 4 for (int i = 1;i<= paramTd.Keys.Count;i++) 5 { 6 paramTd[i] = "string" + i; 7 Console.WriteLine(paramTd[i]); 8 } 9 }
TestForDictionary的执行结果
string1 string2 string3 string4
TestForEachDictionary Code
1 static void TestForEachDictionary(Dictionary<int, string> paramTd) 2 { 3 int forEachCnt = 1; 4 foreach (KeyValuePair<int,string> item in paramTd)//System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' 5 { 6 paramTd[item.Key] = "forEach" + forEachCnt; 7 Console.WriteLine(paramTd[item.Key]); 8 forEachCnt += 1; 9 } 10 }
TestForEachDictionary里的For each会在循环第二次的时候报错,也就是说它会在窗口中打印出“forEach1”后断掉。
学习笔记——异步
1.异步同步的定义
同步方法:多个任务一个一个执行,同一时刻系统中只有一个任务在执行
异步方法:发起一个调用,并不等着计算结束,而是直接去运行下一行;刚才的计算,会启动一个新的线程去执行
2.异步同步的比较
2.1. 同步方法卡界面,因为UI线程忙于计算;异步多线程方法不卡界面,主线程闲置,计算任务交给子线程在做;
2.2. 同步方法慢,只有一个线程计算;异步多线程方法快,多个线程并发计算;
多线程的资源消耗更多,线程并不是越多越好(资源有限/管理线程也消耗资源)
2.3. 异步多线程是无序的:启动无序 执行时间不确定 结束无序,,
所以不要试图通过启动顺序或者时间等待来控制流程
3.异步如何控制执行顺序
3.1.回调
//IasyncResult,可用于监视调用进度
//DoSomethingLong方法名称(要执行的操作)
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = null;
AsyncCallback callback = ar =>
{
// Console.WriteLine(object.ReferenceEquals(ar, iAsyncResult));
Console.WriteLine(ar.AsyncState);
Console.WriteLine($"这里是BeginInvoke调用完成之后才执行的。。。{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
};
iAsyncResult = act.BeginInvoke("btnAsync_Click", callback, "异步学习");
3.2.等待
//IAsyncResult.IsCompeted确定异步调用何时完成(轮询)
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
int i = 1;
while (!iAsyncResult.IsCompleted)//1 卡界面,主线程在等待 2 边等待边做事儿 3有误差(时间)
{
if (i < 10)
{
Console.WriteLine($"文件上传{i++ * 10}%。。。请等待");
}
else
{
Console.WriteLine("已完成99%。。。马上结束");
}
Thread.Sleep(200);//误差时间
}
Console.WriteLine("文件上传成功!!!");
3.3.状态
//使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 //WaitHandle 信号,然后调用
Action<string> act = this.DoSomethingLong;
IAsyncResult iAsyncResult = act.BeginInvoke("btnAsync_Click", null, null);
//异步变同步(状态)
iAsyncResult.AsyncWaitHandle.WaitOne();//一直等待任务完成,第一时间进入下一行
iAsyncResult.AsyncWaitHandle.WaitOne(-1);//一直等待任务完成,第一时间进入下一行
//最多等待1000ms,超时控制
iAsyncResult.AsyncWaitHandle.WaitOne(1000);//最多等待1000ms,否则就进入下一行,可以做一些超时控制
//调用 BeginInvoke 后可随时调用 EndInvoke 方法;如果异步调用未完成,EndInvoke 将一直阻塞到
//异步调用完成。
act.EndInvoke(iAsyncResult);//等待
4.其他相关知识
任何的异步多线程,都是跟委托相关。委托中的Invoke方法 是同步的。BeginInvoke开始一个异步的请求,调用线程池中一个线程来执行。
4.1.异步获取返回值
Func<int, string> func = i => i.ToString();
IAsyncResult iAsyncResult = func.BeginInvoke(DateTime.Now.Year, ar =>
{
string resultIn = func.EndInvoke(ar);//对于每个异步操作,只能调用一次 EndInvoke。
Console.WriteLine($"This is {ar.AsyncState} 的异步调用结果 {resultIn}");
}, "异步参数");
//string result = func.EndInvoke(iAsyncResult);//获取返回值