[译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并...

[译]聊聊C#中的泛型的使用(新手勿入)

 

写在前面

今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发现了一些问题,因此也进行了纠正,当然,原文的地址我放在最下面,如果你的英文水平比较好的话,可以直接直接阅读全文。同时最近建了一个.NET Core实战项目交流群637326624,有兴趣的朋友可以来相互交流。目前.NET Core实战项目之CMS的教程也已经更新了6篇了,目前两到三天更新一篇。

作者:依乐祝
原文地址:https://www.cnblogs.com/yilezhu/p/10029782.html

介绍

C#和.NET中的泛型程序具有强类型集合的许多优点,并为代码提供更高质量和性能提升。泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework。类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大大简化类型之间的强制转换或装箱操作的过程(装箱、拆箱问题)。说白了,泛型就是通过参数化类型来实现在同一份代码上操作多种数据类型,利用“参数化类型”将类型抽象化,从而实现灵活的复用。每个集合的详细规范可以在System.Collection.Generic名称空间下找到。

通用类C#

装箱和拆箱

.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时如下;

IL-opcode.jpg

代码加载常量20并将其存储在本地插槽中;它将值20加载到堆栈中并将其装箱。最后,它将被装箱的20返回到堆栈上,并将其拆箱为int类型

这个过程.NET CLR执行了一系列操作,例如,首先在托管堆中分配一个对象,然后在装箱中将值转换为内存位置,并在拆箱期间将值存储在堆上并且必须转回到堆栈。因此,从性能的角度来看,装箱和拆箱过程在泛型中具有非常重要的意义,因为这个过程如果不使用泛型的话会耗费更多地资源。

泛型类

可以通过在类名后面加上符号来定义泛型类。这里没有强制必须将“T”字放在泛型的定义中。您可以在TestClass <>类声明中使用任何单词。

public class TestClass { }  

System.Collection.Generic命名空间下还定义了许多实现了这些关键字接口的类型。下表列出了此命名空间的核心类类型。

泛型类 描述
Collection 泛型集合的基类,可以比较两个泛型对象是否相等
Dictionary 键值对的泛型集合
List 可动态调整列表项的大小
Queue 先进先出(FIFO)列表的泛型实现
Stack 后进先出(LIFO)列表的泛型实现

简单的泛型类示例

以下示例显示了一个简单的泛型类型的操作。TestClass 定义一个长度为5的泛型类型数组。Add()方法负责将任何类型的对象添加到集合中,而Indexer属性是循环语句迭代的实现。最后在主类中,我们使用整形类型来实例化TestClass 类,并使用Add()方法将一些整数类型数据添加到集合中。

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class TestClass { // 定义一个长度为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(); } } } 

在构建并运行该程序之后,程序的输出如下所示;

简单仿制Example.jpg

泛型的主要特性

泛型类型的一些重要特征使它们相比传统的非泛型类型具有如下的显著特征:

  • 类型安全
  • 性能
  • 二进制代码复用

类型安全

泛型最重要的特征之一是类型安全性。对于非泛型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(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(); } } } 

编译此泛型方法实现的程序后,输出如下所示;

通用-Methods.jpg

字典

字典也被称为映射或散列表。它表示允许您基于关键字来访问元素的数据结构。字典的一个重要特征是更快的查找; 您可以添加或删除选项而不会产生性能开销。

.Net提供了几个字典类,例如Dictionary 。类型参数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

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 children;
 36         public ObservableCollection 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() { 
 28                     new TreeViewItemVM() { DisplayName = "站台", Deepth = 1, HasChildren = true, ID = "1", Type = NodeType.Region,
 29                         Children = new ObservableCollection(){
 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(){
 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 selectedCameras = new ObservableCollection();
 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         /// 
 70         /// 过滤字段
 71         /// 
 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         /// 
 93         /// View
 94         /// 
 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         /// 
113         /// 刷新View
114         /// 
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 
 5     
 6         
23     
24     
25         
26             
27             
28         
29 
30         
32 
33         
36             
37                 
38                     
39                         
45                     
46                 
47             
48         
49     
50 
复制代码

4.在给界面绑定具体的数据

复制代码
 1 using System.Windows;
 2 
 3 namespace TreeViewDemo
 4 {
 5     /// 
 6     /// MainWindow.xaml 的交互逻辑
 7     /// 
 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.运行结果:

 [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并..._第1张图片

[译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并..._第2张图片

 

 

可编辑树Ztree的使用(包括对后台数据库的增删改查)

 

找了很多网上关于Ztree的例子和代码才搞定。

首先,关于Ztree的代码不介绍了,网上下载之后,引用下列四个文件就能使用了。

 

 

[译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并..._第3张图片

 

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 = "";
        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.给大家看一下我的数据库字段设计,附上关于增删改查操作数据库的代码。

[译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并..._第4张图片

 

[译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并..._第5张图片

 

 这里顶级节点的pid为0,其次projectcode是我项目中使用到的,可以不用考虑。state本来是用于ztree中open配置信息用的。默认展开节点的配置。

type是我用于判断此节点是否包是文件节点用的。(包含子节点的为文件夹节点,没有子节点的就是文件节点)

 

4.serveices代码

复制代码
  1  //获取树所有节点显示
  2         public List GetNodeJsonByProject(Guid ProjectId)
  3         {
  4 
  5 
  6             var project = ProjectsRepository.GetByKey(ProjectId);
  7             string TopNode = project.ProjectCode;
  8             List 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() { pn };
 16             }
 17             else//存在顶级节点
 18             {
 19 
 20                 //List nodes = NodesRepository.Entities.Where(t => t.pid == TopNode).OrderBy(t => t.sort).ToList();//顶级节点下面的一级节点
 21                 List 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() { top };
 26                 }
 27                 else//存在子节点,遍历所有的子节点
 28                 {
 29 
 30                     List LIST = new List();
 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 Test(List list, List LIST, List ALLLIST)
 44         {
 45             List NEWLIST = new List();
 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         /// 
 80         /// 修改节点类型
 81         /// 
 82         /// 
 83         /// 
 84         /// 
 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         /// 
101         /// 获取父级节点下面最大的一个排序+1
102         /// 
103         /// 
104         /// 
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         /// 
120         /// 删除此节点时候,要把下面所有子节点删除
121         /// 
122         /// 
123         /// 
124         public OperationResult DeleteNode(string NodeId)
125         {
126             List 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 LIST = new List();
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,用到绑定数据,给控件绑定了数据源,但是数据却没有显示出来,排查代码发现绑定数据源的的成员用的是字段不是属性。

前端代码:

复制代码

  
  

  
  
复制代码

 

后台代码:

复制代码
public Window3()
        {
            InitializeComponent();
            List list = new List()
                {
                    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 td = new Dictionary();
 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 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 paramTd)
 2         {
 3             int forEachCnt = 1;
 4             foreach (KeyValuePair 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 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 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 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 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);//获取返回值

 

 

程序员常说的「哈希表」是个什么鬼?

 
「哈希表」主要作用在于高效查找。 在编程实现中,常常面临着两个问题:存储和查找,存储和查找的效率往往决定了整个程序的效率。 脑补下,你在家里忘记了指甲刀放在哪里,通常要在你家所有抽屉中顺序寻找,直到找到,最差情况下,有N个抽屉,你就要打开N个抽屉。这种存储方式叫数组,查找方法称为「遍历」。 脑补下,你是一个整理控,所有物品必须分门别类放入整理箱,再将整理箱编号,比如1号放入针线,2号放入证件,3号放入细软。这种存储和查找方式称为「哈希」,如果这个时候要查找护照,你不许要再翻所有抽屉,直接可在2号整理箱中获取,通常只用一次查找即可,如何编号整理箱,称为哈希算法。 同样是查找,差距怎么那么大涅~,假设我们有100亿条数据记录,那差距就变得明显,遍历需要查找最多100亿次,最少1次,哈希只需1次。 让我们正式介绍哈希和哈希算法,哈希也称散列,哈希表是一种与数组、链表等不同的数据结构,与他们需要不断的遍历比较来查找的办法,哈希表设计了一个映射关系f(key)= address,根据key来计算存储地址address,这样可以1次查找,f既是存储数据过程中用来指引数据存储到什么位置的函数,也是将来查找这个位置的算法,叫做哈希算法。 让我们举个例子,比如下面这几个人物,按数组存储: 余罪(133123111243)=>傅老大(13888888888)=>沈嘉文(13452342349)=>大胸姐(13890380934) 这样我要找到大胸姐的电话号码,需要顺序查找对比整个数组,第一个余罪,不是,第二个不是,第三个不是,直到第四个找到大胸姐。 如果以hash存储呢?首先让我们来看看如何设计哈希算法,哈希算法可以随意设计,教科书上一般会说以下几种方法:直接定址发,平方取中法,除数取余法,哈希算法的本质上是计算一个数字,如果用这几种方法讲解会稍显晦涩,我们假设我们的哈希算法是取姓名的首字母。所以f(余罪) = y, f(傅老大) = f,f(沈嘉文) = s,f(大胸姐) = d。 构建的hash表如下: a b c y .133123111243 d .13890380934 g z 位置7 f .13888888888 s .13452342349 我们看到他们分别以姓名首字母的位置插入到这一张表格中,这样我们构建了这样一个Key-Value表格,此表就是哈希表,也称为Hash Table。未来,当我们要查找余罪的时候,通过计算,余罪在y位置,可以通过1次查找,找到这条记录,也即手机号。 这个时候有客官问了,那以首字母为哈希函数的话,应该有很多比如以y的姓名啊,这个时候就不是一次查找了吧,其实有很多条记录都映射到一个位置上,称为哈希冲突。 哈希冲突是跟哈希函数的设计正相关的,你的随机性越大,那么产生哈希冲突的可能性越小,在小概率下,如果还有冲突怎么办,这个时候要做些有损的设计,比如如果有两个首字母为y的姓名,那么可以接到余罪的后面,当查找的时候,需要先查找到y,然后再顺序查找,如下所示: a b c y .133123111243 1332232323于谦 d .13890380934 g z 位置7 f .13888888888 s .13452342349 还有一些解决哈希冲突的办法叫「哈希再哈希」,也就是针对第一次哈希的结果再进行一次hash来减小冲突的概率。 这就是Hash表,首先Ta是一种数据结构,是一种效率极高的查找方式,哈希表的核心在于哈希函数的设计,哈希冲突了不要紧,我们要增加随机性以及对冲突进行适当的有损化的处理。

转载于:https://www.cnblogs.com/cjm123/p/10033875.html

你可能感兴趣的:(c#,数据库,json)