- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- C#泛型的用法及實(shí)例詳解
這篇文章主要講解了“C#泛型的用法及實(shí)例詳解”,文中的講解內容簡(jiǎn)單清晰,易于學(xué)習與理解,下面請大家跟著(zhù)小編的思路慢慢深入,一起來(lái)研究和學(xué)習“C#泛型的用法及實(shí)例詳解”吧!
一、什么是泛型
二、為什么使用泛型
三、泛型類(lèi)型參數
四、泛型類(lèi)
五、泛型約束
六、泛型的協(xié)變和逆變
七、泛型緩存
這篇文章主要講解C#中的泛型,泛型在C#中有很重要的地位,尤其是在搭建項目框架的時(shí)候。
泛型是C#2.0推出的新語(yǔ)法,不是語(yǔ)法糖,而是2.0由框架升級提供的功能。
我們在編程程序時(shí),經(jīng)常會(huì )遇到功能非常相似的模塊,只是它們處理的數據不一樣。但我們沒(méi)有辦法,只能分別寫(xiě)多個(gè)方法來(lái)處理不同的數據類(lèi)型。這個(gè)時(shí)候,那么問(wèn)題來(lái)了,有沒(méi)有一種辦法,用同一個(gè)方法來(lái)處理傳入不同種類(lèi)型參數的辦法呢?泛型的出現就是專(zhuān)門(mén)來(lái)解決這個(gè)問(wèn)題的。
先來(lái)看下面一個(gè)例子:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class CommonMethod { /// <summary> /// 打印個(gè)int值 /// </summary> /// <param name="iParameter"></param> public static void ShowInt(int iParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter); } /// <summary> /// 打印個(gè)string值 /// </summary> /// <param name="sParameter"></param> public static void ShowString(string sParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter); } /// <summary> /// 打印個(gè)DateTime值 /// </summary> /// <param name="oParameter"></param> public static void ShowDateTime(DateTime dtParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter); } } }
結果:
從上面的結果中我們可以看出這三個(gè)方法,除了傳入的參數不同外,其里面實(shí)現的功能都是一樣的。在1.0版的時(shí)候,還沒(méi)有泛型這個(gè)概念,那么怎么辦呢。相信很多人會(huì )想到了OOP三大特性之一的繼承,我們知道,C#語(yǔ)言中,object是所有類(lèi)型的基類(lèi),將上面的代碼進(jìn)行以下優(yōu)化:
public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod), oParameter.GetType().Name, oParameter); }
結果:
從上面的結果中我們可以看出,使用Object類(lèi)型達到了我們的要求,解決了代碼的可復用??赡苡腥藭?huì )問(wèn)定義的是object類(lèi)型的,為什么可以傳入int、string等類(lèi)型呢?原因有二:
1、object類(lèi)型是一切類(lèi)型的父類(lèi)。
2、通過(guò)繼承,子類(lèi)擁有父類(lèi)的一切屬性和行為,任何父類(lèi)出現的地方,都可以用子類(lèi)來(lái)代替。
但是上面object類(lèi)型的方法又會(huì )帶來(lái)另外一個(gè)問(wèn)題:裝箱和拆箱,會(huì )損耗程序的性能。
微軟在C#2.0的時(shí)候推出了泛型,可以很好的解決上面的問(wèn)題。
在泛型類(lèi)型或方法定義中,類(lèi)型參數是在其實(shí)例化泛型類(lèi)型的一個(gè)變量時(shí),客戶(hù)端指定的特定類(lèi)型的占位符。 泛型類(lèi)(GenericList<T>
)無(wú)法按原樣使用,因為它不是真正的類(lèi)型;它更像是類(lèi)型的藍圖。 若要使用GenericList<T>
,客戶(hù)端代碼必須通過(guò)指定尖括號內的類(lèi)型參數來(lái)聲明并實(shí)例化構造類(lèi)型。 此特定類(lèi)的類(lèi)型參數可以是編譯器可識別的任何類(lèi)型。 可創(chuàng )建任意數量的構造類(lèi)型實(shí)例,其中每個(gè)使用不同的類(lèi)型參數。
上面例子中的代碼可以修改如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class GenericMethod { /// <summary> /// 泛型方法 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); } } }
調用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { class Program { static void Main(string[] args) { int iValue = 123; string sValue = "456"; DateTime dtValue = DateTime.Now; Console.WriteLine("***********CommonMethod***************"); CommonMethod.ShowInt(iValue); CommonMethod.ShowString(sValue); CommonMethod.ShowDateTime(dtValue); Console.WriteLine("***********Object***************"); CommonMethod.ShowObject(iValue); CommonMethod.ShowObject(sValue); CommonMethod.ShowObject(dtValue); Console.WriteLine("***********Generic***************"); GenericMethod.Show<int>(iValue); GenericMethod.Show<string>(sValue); GenericMethod.Show<DateTime>(dtValue); Console.ReadKey(); } } }
顯示結果:
為什么泛型可以解決上面的問(wèn)題呢?
泛型是延遲聲明的:即定義的時(shí)候沒(méi)有指定具體的參數類(lèi)型,把參數類(lèi)型的聲明推遲到了調用的時(shí)候才指定參數類(lèi)型。 延遲思想在程序架構設計的時(shí)候很受歡迎。例如:分布式緩存隊列、EF的延遲加載等等。
泛型究竟是如何工作的呢?
控制臺程序最終會(huì )編譯成一個(gè)exe程序,exe被點(diǎn)擊的時(shí)候,會(huì )經(jīng)過(guò)JIT(即時(shí)編譯器)的編譯,最終生成二進(jìn)制代碼,才能被計算機執行。泛型加入到語(yǔ)法以后,VS自帶的編譯器又做了升級,升級之后編譯時(shí)遇到泛型,會(huì )做特殊的處理:生成占位符。再次經(jīng)過(guò)JIT編譯的時(shí)候,會(huì )把上面編譯生成的占位符替換成具體的數據類(lèi)型。請看下面一個(gè)例子:
Console.WriteLine(typeof(List<>)); Console.WriteLine(typeof(Dictionary<,>));
結果:
從上面的截圖中可以看出:泛型在編譯之后會(huì )生成占位符。
注意:占位符需要在英文輸入法狀態(tài)下才能輸入,只需要按一次波浪線(xiàn)(數字1左邊的鍵位)的鍵位即可,不需要按Shift鍵。
1、泛型性能問(wèn)題
請看一下的一個(gè)例子,比較普通方法、Object參數類(lèi)型的方法、泛型方法的性能。
添加一個(gè)Monitor類(lèi),讓三種方法執行同樣的操作,比較用時(shí)長(cháng)短:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class Monitor { public static void Show() { Console.WriteLine("****************Monitor******************"); { int iValue = 12345; long commonSecond = 0; long objectSecond = 0; long genericSecond = 0; { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { ShowInt(iValue); } watch.Stop(); commonSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { ShowObject(iValue); } watch.Stop(); objectSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { Show<int>(iValue); } watch.Stop(); genericSecond = watch.ElapsedMilliseconds; } Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}" , commonSecond, objectSecond, genericSecond); } } #region PrivateMethod private static void ShowInt(int iParameter) { //do nothing } private static void ShowObject(object oParameter) { //do nothing } private static void Show<T>(T tParameter) { //do nothing } #endregion } }
Main()方法調用:
Monitor.Show();
結果:
從結果中可以看出:泛型方法的性能最高,其次是普通方法,object方法的性能最低。
除了方法可以是泛型以外,類(lèi)也可以是泛型的,例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { /// <summary> /// 泛型類(lèi) /// </summary> /// <typeparam name="T"></typeparam> public class GenericClass<T> { public T _T; } }
Main()方法中調用:
// T是int類(lèi)型 GenericClass<int> genericInt = new GenericClass<int>(); genericInt._T = 123; // T是string類(lèi)型 GenericClass<string> genericString = new GenericClass<string>(); genericString._T = "123";
除了可以有泛型類(lèi),也可以有泛型接口,例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { /// <summary> /// 泛型接口 /// </summary> public interface IGenericInterface<T> { //泛型類(lèi)型的返回值 T GetT(T t); } }
也可以有泛型委托:
public delegate void SayHi<T>(T t);//泛型委托
注意:
1、泛型在聲明的時(shí)候可以不指定具體的類(lèi)型,但是在使用的時(shí)候必須指定具體類(lèi)型,例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { /// <summary> /// 使用泛型的時(shí)候必須指定具體類(lèi)型, /// 這里的具體類(lèi)型是int /// </summary> public class CommonClass :GenericClass<int> { } }
如果子類(lèi)也是泛型的,那么繼承的時(shí)候可以不指定具體類(lèi)型,例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { /// <summary> /// 使用泛型的時(shí)候必須指定具體類(lèi)型, /// 這里的具體類(lèi)型是int /// </summary> public class CommonClass :GenericClass<int> { } /// <summary> /// 子類(lèi)也是泛型的,繼承的時(shí)候可以不指定具體類(lèi)型 /// </summary> /// <typeparam name="T"></typeparam> public class CommonClassChild<T>:GenericClass<T> { } }
2、類(lèi)實(shí)現泛型接口也是這種情況,例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { /// <summary> /// 必須指定具體類(lèi)型 /// </summary> public class Common : IGenericInterface<string> { public string GetT(string t) { throw new NotImplementedException(); } } /// <summary> /// 可以不知道具體類(lèi)型,但是子類(lèi)也必須是泛型的 /// </summary> /// <typeparam name="T"></typeparam> public class CommonChild<T> : IGenericInterface<T> { public T GetT(T t) { throw new NotImplementedException(); } } }
先來(lái)看看下面的一個(gè)例子:
定義一個(gè)People類(lèi),里面有屬性和方法:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public interface ISports { void Pingpang(); } public interface IWork { void Work(); } public class People { public int Id { get; set; } public string Name { get; set; } public void Hi() { Console.WriteLine("Hi"); } } public class Chinese : People, ISports, IWork { public void Tradition() { Console.WriteLine("仁義禮智信,溫良恭儉讓"); } public void SayHi() { Console.WriteLine("吃了么?"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } public void Work() { throw new NotImplementedException(); } } public class Hubei : Chinese { public Hubei(int version) { } public string Changjiang { get; set; } public void Majiang() { Console.WriteLine("打麻將啦。。"); } } public class Japanese : ISports { public int Id { get; set; } public string Name { get; set; } public void Hi() { Console.WriteLine("Hi"); } public void Pingpang() { Console.WriteLine("打乒乓球..."); } } }
在Main()方法里面實(shí)例化:
People people = new People() { Id = 123, Name = "走自己的路" }; Chinese chinese = new Chinese() { Id = 234, Name = "晴天" }; Hubei hubei = new Hubei(123) { Id = 345, Name = "流年" }; Japanese japanese = new Japanese() { Id = 7654, Name = "werwer" };
這時(shí)有一個(gè)需求:需要打印出Id和Name屬性的值,將ShowObject()方法修改如下:
但是這樣修改報錯了:object類(lèi)里面沒(méi)有Id和Name屬性,可能會(huì )有人說(shuō),強制類(lèi)型轉換一下就行了?。?/p>
public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(CommonMethod), oParameter.GetType().Name, oParameter); Console.WriteLine($"{((People)oParameter).Id}_{((People)oParameter).Name}"); }
這樣修改以后,代碼不會(huì )報錯了,這時(shí)我們在Main()方法里面調用:
CommonMethod.ShowObject(people); CommonMethod.ShowObject(chinese); CommonMethod.ShowObject(hubei); CommonMethod.ShowObject(japanese);
結果:
可以看出程序報錯了,因為Japanese沒(méi)有繼承自People,這里類(lèi)型轉換的時(shí)候失敗了。這樣會(huì )造成類(lèi)型不安全的問(wèn)題。那么怎么解決類(lèi)型不安全的問(wèn)題呢?那就是使用泛型約束。
所謂的泛型約束,實(shí)際上就是約束的類(lèi)型T。使T必須遵循一定的規則。比如T必須繼承自某個(gè)類(lèi),或者T必須實(shí)現某個(gè)接口等等。那么怎么給泛型指定約束?其實(shí)也很簡(jiǎn)單,只需要where關(guān)鍵字,加上約束的條件。
泛型約束總共有五種。
1、基類(lèi)約束
上面打印的方法約束T類(lèi)型必須是People類(lèi)型。
/// <summary> /// 基類(lèi)約束:約束T必須是People類(lèi)型或者是People的子類(lèi) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tParameter"></param> public static void Show<T>(T tParameter) where T : People { Console.WriteLine($"{tParameter.Id}_{tParameter.Name}"); tParameter.Hi(); }
注意:
基類(lèi)約束時(shí),基類(lèi)不能是密封類(lèi),即不能是sealed類(lèi)。sealed類(lèi)表示該類(lèi)不能被繼承,在這里用作約束就無(wú)任何意義,因為sealed類(lèi)沒(méi)有子類(lèi)。
2、接口約束
/// <summary> /// 接口約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : ISports { t.Pingpang(); return t; }
3、引用類(lèi)型約束 class
引用類(lèi)型約束保證T一定是引用類(lèi)型的。
/// <summary> /// 引用類(lèi)型約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : class { return t; }
4、值類(lèi)型約束 struct
值類(lèi)型約束保證T一定是值類(lèi)型的。
/// <summary> /// 值類(lèi)型類(lèi)型約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : struct { return t; }
5、無(wú)參數構造函數約束 new()
/// <summary> /// new()約束 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T Get<T>(T t) where T : new() { return t; }
泛型約束也可以同時(shí)約束多個(gè),例如:
public static void Show<T>(T tParameter) where T : People, ISports, IWork, new() { Console.WriteLine($"{tParameter.Id}_{tParameter.Name}"); tParameter.Hi(); tParameter.Pingpang(); tParameter.Work(); }
注意:有多個(gè)泛型約束時(shí),new()約束一定是在最后。
協(xié)變和逆變是在.NET 4.0的時(shí)候出現的,只能放在接口或者委托的泛型參數前面,out 協(xié)變covariant,用來(lái)修飾返回值;in:逆變contravariant,用來(lái)修飾傳入參數。
先看下面的一個(gè)例子:
定義一個(gè)Animal類(lèi):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class Animal { public int Id { get; set; } } }
然后在定義一個(gè)Cat類(lèi)繼承自Animal類(lèi):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class Cat :Animal { public string Name { get; set; } } }
在Main()方法可以這樣調用:
// 直接聲明Animal類(lèi) Animal animal = new Animal(); // 直接聲明Cat類(lèi) Cat cat = new Cat(); // 聲明子類(lèi)對象指向父類(lèi) Animal animal2 = new Cat(); // 聲明Animal類(lèi)的集合 List<Animal> listAnimal = new List<Animal>(); // 聲明Cat類(lèi)的集合 List<Cat> listCat = new List<Cat>();
那么問(wèn)題來(lái)了:下面的一句代碼是不是正確的呢?
List<Animal> list = new List<Cat>();
可能有人會(huì )認為是正確的:因為一只Cat屬于A(yíng)nimal,那么一群Cat也應該屬于A(yíng)nimal啊。但是實(shí)際上這樣聲明是錯誤的:因為L(cháng)ist<Cat>和List<Animal>之間沒(méi)有父子關(guān)系。
這時(shí)就可以用到協(xié)變和逆變了。
// 協(xié)變 IEnumerable<Animal> List1 = new List<Animal>(); IEnumerable<Animal> List2 = new List<Cat>();
F12查看定義:
可以看到,在泛型接口的T前面有一個(gè)out關(guān)鍵字修飾,而且T只能是返回值類(lèi)型,不能作為參數類(lèi)型,這就是協(xié)變。使用了協(xié)變以后,左邊聲明的是基類(lèi),右邊可以聲明基類(lèi)或者基類(lèi)的子類(lèi)。
協(xié)變除了可以用在接口上面,也可以用在委托上面:
Func<Animal> func = new Func<Cat>(() => null);
除了使用.NET框架定義好的以為,我們還可以自定義協(xié)變,例如:
/// <summary> /// out 協(xié)變 只能是返回結果 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListOut<out T> { T Get(); } public class CustomerListOut<T> : ICustomerListOut<T> { public T Get() { return default(T); } }
使用自定義的協(xié)變:
// 使用自定義協(xié)變 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>(); ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
在來(lái)看看逆變。
在泛型接口的T前面有一個(gè)In關(guān)鍵字修飾,而且T只能方法參數,不能作為返回值類(lèi)型,這就是逆變。請看下面的自定義逆變:
/// <summary> /// 逆變 只能是方法參數 /// </summary> /// <typeparam name="T"></typeparam> public interface ICustomerListIn<in T> { void Show(T t); } public class CustomerListIn<T> : ICustomerListIn<T> { public void Show(T t) { } }
使用自定義逆變:
// 使用自定義逆變 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>(); ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
協(xié)變和逆變也可以同時(shí)使用,看看下面的例子:
/// <summary> /// inT 逆變 /// outT 協(xié)變 /// </summary> /// <typeparam name="inT"></typeparam> /// <typeparam name="outT"></typeparam> public interface IMyList<in inT, out outT> { void Show(inT t); outT Get(); outT Do(inT t); } public class MyList<T1, T2> : IMyList<T1, T2> { public void Show(T1 t) { Console.WriteLine(t.GetType().Name); } public T2 Get() { Console.WriteLine(typeof(T2).Name); return default(T2); } public T2 Do(T1 t) { Console.WriteLine(t.GetType().Name); Console.WriteLine(typeof(T2).Name); return default(T2); } }
使用:
IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>(); IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//協(xié)變 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆變 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//逆變+協(xié)變
在前面我們學(xué)習過(guò),類(lèi)中的靜態(tài)類(lèi)型無(wú)論實(shí)例化多少次,在內存中只會(huì )有一個(gè)。靜態(tài)構造函數只會(huì )執行一次。在泛型類(lèi)中,T類(lèi)型不同,每個(gè)不同的T類(lèi)型,都會(huì )產(chǎn)生一個(gè)不同的副本,所以會(huì )產(chǎn)生不同的靜態(tài)屬性、不同的靜態(tài)構造函數,請看下面的例子:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyGeneric { public class GenericCache<T> { static GenericCache() { Console.WriteLine("This is GenericCache 靜態(tài)構造函數"); _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } private static string _TypeTime = ""; public static string GetCache() { return _TypeTime; } } }
然后新建一個(gè)測試類(lèi),用來(lái)測試GenericCache類(lèi)的執行順序:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MyGeneric { public class GenericCacheTest { public static void Show() { for (int i = 0; i < 5; i++) { Console.WriteLine(GenericCache<int>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<long>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<DateTime>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<string>.GetCache()); Thread.Sleep(10); Console.WriteLine(GenericCache<GenericCacheTest>.GetCache()); Thread.Sleep(10); } } } }
Main()方法里面調用:
GenericCacheTest.Show();
結果:
從上面的截圖中可以看出,泛型會(huì )為不同的類(lèi)型都創(chuàng )建一個(gè)副本,所以靜態(tài)構造函數會(huì )執行5次。 而且每次靜態(tài)屬性的值都是一樣的。利用泛型的這一特性,可以實(shí)現緩存。
注意:只能為不同的類(lèi)型緩存一次。泛型緩存比字典緩存效率高。泛型緩存不能主動(dòng)釋放
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站