(转)彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter
此文轉(zhuǎn)自喆 喆的博客,原文鏈接地址:http://www.cnblogs.com/szp1118/archive/2010/07/31/1789148.html
自從使用.net以來就一直都在使用string.Format方法,一直沒有空或者其他原因都沒有深入去了解,主要還是因為項目上似乎沒有這么高的要求,也沒必要去深入了解,就算碰到了自定義的格式化內(nèi)容也是寫幾個通用的方法而已。今天空下來仔細去理解了一下,在這里和大家分享一下,也希望大家一起交流。
?
string.Format方法是string類提供的靜態(tài)方法,一般最多使用的是其兩個參數(shù)的重載,例如:
?
var?name?=?"Zhezhe";var?msg?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",?name,?DateTime.Now,?DateTime.Now.DayOfWeek);
Console.WriteLine(msg);
?
后面一個參數(shù)是.net語法簡寫的可變參數(shù),在.net內(nèi)部實際是數(shù)組而已,實質(zhì)還是兩個參數(shù)的方法重載。
你也可以不使用這種方法,將字符串相加即可
var?msg1?=?"Hello?Cnblogs,?I?am?"?+?name?+?",Today?is?"?+?DateTime.Now.ToString("yyyy-MM-dd")?+?"?"?+?DateTime.Now.DayOfWeek?+?".";上面兩種方法的結(jié)果是一樣的。
?之前普遍使用第一種方法的原因是相比string的多個加號相加在性能上有一定優(yōu)勢,因為其內(nèi)部是使用StringBuilder類的,還有一個原因是代碼的可讀性比起+這樣的方式更好一些。
?
分析一下第一種方法的實現(xiàn)原理:
?1.Format方法的內(nèi)部解析方式和原理
Format方法在取到第一個參數(shù)"Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}."之后便將其分解成多個部分
①?"Hello?Cnblogs,?I?am?" ??② "{0}" ?③",Today?is " ④"{1:yyyy-MM-dd}"⑤ "? " ⑥ "{2}"⑦ "."
分解的原則是按照{(diào)}配對的數(shù)量進行的,{}是微軟定義好的標記而已,你自己也可以去實現(xiàn)個用 []表示都無所謂。既然{}已經(jīng)被定義為了特殊的標記,所以如果是自己需要在字符串中包含大括號的話就必須進行轉(zhuǎn)義,這個轉(zhuǎn)義也和我們平時使用的"/"轉(zhuǎn)義表示法不同,需要使用兩個大括號進行轉(zhuǎn)義如 {{ 或者 }}。 如:
?var?msg2?=?string.Format("Hello?{{}},I?am?{0}",?name);將{}分解出來之后根據(jù)中間的序號來對應(yīng)第二個參數(shù),如果第二個參數(shù)的實際個數(shù)小于需要的數(shù)量,則會出現(xiàn)運行錯誤(編譯時不會報錯), 如果參數(shù)個數(shù)大于序號的數(shù)量,則其后的忽略不計。
?參數(shù)個數(shù)小于序號的實際數(shù)量,錯誤
var?msg4?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",?name,?DateTime.Now);參數(shù)個數(shù)大于序號的實際數(shù)量,多出的參數(shù)忽略不計
?var?msg4?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}.",?name,?DateTime.Now,DateTime.Now.DayOfWeek);序號的順序不一定必須是0,1,2,3,4可以任意排列,但是序號永遠和第二個參數(shù)(實質(zhì)是數(shù)組)的索引一致。
var?msg4?=?string.Format("Hello?Cnblogs,?I?am?{2},Today?is?{0:yyyy-MM-dd}?{1}.",?DateTime.Now,?DateTime.Now.DayOfWeek,?name);序號還能跳躍,但是中間跳躍過的序號參數(shù)里必須有
var?msg5?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{2:yyyy-MM-dd}?{3}.",?name,?"test",?DateTime.Now,?DateTime.Now.DayOfWeek);?上面講了一下用法,接下來繼續(xù)
分解完畢之后使用 StringBuilder的Append方法將各個部分添加進去,最后再用ToString方法轉(zhuǎn)成string,其實現(xiàn)原理非常類似于下面的代碼
var?s?=?new?StringBuilder();????????????s.Append("Hello?Cnblogs,?I?am?");
????????????s.Append(name);
????????????s.Append(",Today?is?");
????????????s.Append(DateTime.Now.ToString("yyyy-MM-dd"));
????????????s.Append("?");
????????????s.Append(DateTime.Now.DayOfWeek);
????????????s.Append(".");
????????????var?msg3?=?s.ToString();
順便解釋一下string和StringBuilder:string雖然也是引用類型,但是該類型.net內(nèi)部進行了特殊處理,讓其表現(xiàn)出和值類型相似的特征,特別是在每次變動之后就會重新分配內(nèi)存空間,而StringBuilder就不會,所以如果有很多個字符串相加拼接,則string性能較低。
在用 Append方法進行添加的時候會有兩種情況:
一種是{0},{1}這樣的不帶有特殊格式化的則直接會調(diào)用該對象的ToString方法,比如上面的 ?s.Append(DateTime.Now.DayOfWeek);其實就是 s.Append(DateTime.Now.DayOfWeek.ToString());在.net中,如果是自己定義的類,并且沒有重寫ToString方法,則會輸出類的全名,下面會詳細討論。
另一種是{0:yyyy-MM-dd}帶有特殊格式化的則繼續(xù)分解,將冒號后面的內(nèi)容分解出來,并且在調(diào)用ToString時作為參數(shù)傳入,上面的s.Append(DateTime.Now.ToString("yyyy-MM-dd"));就體現(xiàn)了這一點。所以這些其實都沒什么奧妙可言,冒號也是一個預(yù)定義好的標記而已,如果微軟讓你去實現(xiàn)這個,你也可以用其他符號。
?
2.ToString方法的深入理解
通過第一步的分析如果純粹從分析Format這個方法來說已經(jīng)足夠了,大括號的特殊標記作用以及和后面參數(shù)的對應(yīng)關(guān)系也已經(jīng)解釋清楚了。但是這里還是需要深入了解一下ToString方法。
上面1中提到如果一個自己定義的類不去重寫ToString方法的話則會 輸出類的全名,例如
public?class?Person????{
????????public?string?Name?{?get;?set;?}
????}
如果寫如下代碼
var?msg6?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",?????????????????????????????????????new?Person()?{Name?=?"Zhezhe"},?DateTime.Now,?DateTime.Now.DayOfWeek);
????????????Console.WriteLine(msg6);
則會輸出:
?這里再次強調(diào)一下,如果某個對象需要轉(zhuǎn)換成ToString,并且沒有手動調(diào)用該方法,程序會自動調(diào)用該方法,上面的new?Person()?{Name?=?"Zhezhe"}沒有手工調(diào)用,程序會自動調(diào)用方法(new?Person()?{Name?=?"Zhezhe"}).ToString(); 這個是微軟讓你少些代碼而已,好的習(xí)慣是始終寫上 .ToString();
.net中的任何對象都具有該方法,因為該方法在object對象中定義,任何類或者結(jié)構(gòu)都會繼承object,所以不用擔心一個對象沒有ToString方法。
接下來定義帶有ToString重載方法的類
public?class?PersonWithToString????{
????????public?string?Name?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?Name;
????????}
????}
編寫如下代碼:
?//使用自己定義類,但是重寫了ToString方法var?msg7?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",?new?PersonWithToString(){?Name?=?"Zhezhe"?},?DateTime.Now,?DateTime.Now.DayOfWeek);
Console.WriteLine(msg7);
輸入結(jié)果為 輸出就正常了,自己重寫的方法起作用了。
總結(jié):對自己定義的類始終重寫 ToString方法。 這樣在 string.Format 中或者其他需要程序自動轉(zhuǎn)換成string類型時不會出現(xiàn) 輸出類全名的情況。
?
3.ToString帶有自定義格式化參數(shù)的理解
上面講到的ToString都是不帶格式化參數(shù)的,像 ?{1:yyyy-MM-dd} 這樣的情況是沒法處理的,也許有人會說像 DateTime.Now.ToString("yyyy-MM-dd") 這樣的情況自己去重載一個ToString方法就可以了,果真如此嗎? 下面就測試一下
public?class?PersonWithToString????{
????????public?string?Name?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?Name;
????????}
????????public?string?ToString(string?format)
????????{
????????????switch?(format)
????????????{
????????????????case?"UPP":
????????????????????return?Name.ToUpper();
????????????????case?"LOW":
????????????????????return?Name.ToLower();
????????????????default:
????????????????????return?Name;
????????????}
????????}
????}
?
var?msg9?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",??????????????????????????????????new?PersonWithToString()?{?Name?=?"Zhezhe"?}.ToString("UPP"),?DateTime.Now,?DateTime.Now.DayOfWeek);
????????????Console.WriteLine(msg9);
msg9的實際輸出為 Hello Cnblogs, I am ZHEZHE,Today is 2010-07-30 Friday.? 這個正是我們需要的,當然,這個肯定是對的,要不然就是.net的bug了
接下來再看看下面的
var?msg8?=?string.Format("Hello?Cnblogs,?I?am?{0:UPP},Today?is?{1:yyyy-MM-dd}?{2}.",??????????????????????????????????new?PersonWithToString()?{?Name?=?"Zhezhe"?},?DateTime.Now,?DateTime.Now.DayOfWeek);
實際輸出是: Hello Cnblogs, I am Zhezhe,Today is 2010-07-30 Friday.? 并不是我們所期望的。實際上上面的代碼是調(diào)用了PersonWithToString類的不帶參數(shù)的ToString()方法。言外之意就是? ?{0:UPP}這樣的格式實際上內(nèi)部處理的是和 ?{0}
一樣的效果了。在1中提到了分解的原理用了類似兩個字,實際情況并不是這么簡單。
?? {0:UPP} 真正調(diào)用的方法簽名是??? string ToString(string format,IFormatProvider formatProvider)
而且也不是直接調(diào)用該對象的此方法。而是通過?IFormattable 接口實現(xiàn)的方法
?現(xiàn)在定義實現(xiàn)了該接口的 Person2類
?
Person2 public?class?Person2?:?IFormattable????{
????????public?string?Name?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?Name;
????????}
????????#region?IFormattable?Members
????????public?string?ToString(string?format,?IFormatProvider?formatProvider)
????????{
????????????if?(string.IsNullOrEmpty(format))
????????????????return?ToString();
????????????switch?(format)
????????????{
????????????????case?"UPP":
????????????????????return?Name.ToUpper();
????????????????case?"LOW":
????????????????????return?Name.ToLower();
????????????????default:
????????????????????return?Name;
????????????}
????????}
????????#endregion
????}
?運行一下代碼得到預(yù)期的結(jié)果
??//使用實現(xiàn)了IFormattable接口的Person2對象var?msg10?=?string.Format("Hello?Cnblogs,?I?am?{0:UPP},Today?is?{1:yyyy-MM-dd}?{2}.",
new?Person2()?{?Name?=?"ZhezheToUpper"?},?DateTime.Now,?DateTime.Now.DayOfWeek);
Console.WriteLine(msg10);
?ZhezheToUpper已經(jīng)輸出成全部大寫形式了。
?
既然{0:UPP}會調(diào)用接口定義的ToString方法,那么{0}呢? 如果該類沒有實現(xiàn)IFormattable接口,上面已經(jīng)說了,會調(diào)用重載的或者是基類的ToString()方法。但是如果該類已經(jīng)實現(xiàn)了IFormattable接口,那么{0}也不會去調(diào)用重載的或者是基類的ToString()方法了,它始終是去調(diào)用 接口定義的 ToString方法。下面具體印證一下
?
Person3 ?public?class?Person3?:?IFormattable????{
????????public?string?Name?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?Name;
????????}
????????#region?IFormattable?Members
????????public?string?ToString(string?format,?IFormatProvider?formatProvider)
????????{
????????????if?(string.IsNullOrEmpty(format))
????????????????return?Name?+?"?IFormattable?Method";
????????????switch?(format)
????????????{
????????????????case?"UPP":
????????????????????return?Name.ToUpper();
????????????????case?"LOW":
????????????????????return?Name.ToLower();
????????????????default:
????????????????????return?Name?+?"?IFormattable?Method";
????????????}
????????}
????????#endregion
????}
?
運行下面的測試代碼
var?msg11?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",??????????????????????????????????new?Person3()?{?Name?=?"ZhezheToUpper"?},?DateTime.Now,?DateTime.Now.DayOfWeek);
????????????Console.WriteLine(msg11);?
?
輸出為: Hello Cnblogs, I am ZhezheToUpper IFormattable Method,Today is 2010-07-30 Friday.
證明了確實是調(diào)用了接口定義的方法,而不是重載的ToString方法,否則是輸出ZhezheToUpper
再來看一下Person2中實現(xiàn)的ToString方法,
?if (string.IsNullOrEmpty(format))
??????????????? return ToString();
如果是剛才的{0}不帶格式化參數(shù)的調(diào)用,則format參數(shù)傳過來的是null值,這里需要自己判斷,如果是null值,一般情況下是手工去調(diào)用重載的ToString()方法。
所以Person2的做法是好的,而Person3中的做法是不好的,Person3只是為了測試分辨出調(diào)用的是哪個方法才這么設(shè)計的。
?
總結(jié):一.對于實現(xiàn)IFormattable 接口時,如果format參數(shù)為null(即不帶格式化參數(shù)的情況,如{0})則應(yīng)該調(diào)用重載的 ToString()方法,而不應(yīng)該自己去另外寫代碼。
二.如果找不到相應(yīng)的格式化參數(shù),例如{0:AAA},在Person2的switch中并無匹配的AAA,這種情況一般也應(yīng)該去調(diào)用重載的 ToString()方法。
否則就會出現(xiàn)
?
?//以下兩個輸出結(jié)果不一樣,是不合理的var?msg12?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",new?Person3()?{?Name?=?"ZhezheToUpper"?},?DateTime.Now,?DateTime.Now.DayOfWeek);
Console.WriteLine(msg12);
var?msg13?=?string.Format("Hello?Cnblogs,?I?am?{0},Today?is?{1:yyyy-MM-dd}?{2}.",new?Person3()?{?Name?=?"ZhezheToUpper"?}.ToString(),?DateTime.Now,?DateTime.Now.DayOfWeek);
Console.WriteLine(msg13);
?
不同的結(jié)果的情況
上面的輸出結(jié)果不同:
?這是不好的設(shè)計
?
?4.繼續(xù)了解 IFormatProvider?和 ICustomFormatter 接口
到這里為止,應(yīng)該說靈活應(yīng)用string.Format()已經(jīng)沒什么多大的問題了,但是也還是存在一些問題,比如我們必須得為每個類單獨去實現(xiàn)IFormattable接口才能實現(xiàn)自定義的格式化參數(shù)。在一些場后還是覺得不太方便或者說代碼冗余。
.net的string.Format靜態(tài)方法還提供了重載方法,具體簽名如下:public static string Format(IFormatProvider provider,string format,params Object[] args)
?
這個方法比起原來使用的方法最前面增加了 IFormatProvider類型參數(shù)。使用此方法的優(yōu)點是不需要為后面的參數(shù)對象實現(xiàn) IFormattable? 接口就可以使用自定義的格式化參數(shù)。既然這樣的話也就解決了第4部分開頭提到的問題了。
?
還是用例子說話吧
下面是正方形類
Square類 public?class?Square????{
????????public?string?Name?{?get;?set;?}
????????///?<summary>
????????///?邊長
????????///?</summary>
????????public?double?Side?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?string.Format("{0}(Side:{1})",Name,?Side);
????????}
????}
?
下面是長方形類
?
Rectangle類 ?public?class?Rectangle????{
????????public?string?Name?{?get;?set;?}
????????///?<summary>
????????///?寬
????????///?</summary>
????????public?double?Width?{?get;?set;?}
????????///?<summary>
????????///?高
????????///?</summary>
????????public?double?Height?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?string.Format("{0}(Width:{1},Height:{2})",Name,?Width,?Height);
????????}
????}
?
?兩個類都重寫了ToString方法
定義MyHelloFormatProvider類,該類從名稱上就可以看出是格式化的提供者
?
?public?class?MyHelloFormatProvider?:?IFormatProvider????{
????????#region?IFormatProvider?Members
????????public?object?GetFormat(Type?formatType)
????????{
????????????return?new?MyHelloFormatter();
????????}
????????#endregion
????}
?
該類實現(xiàn)了 IFormatProvider 接口,接口只有一個唯一的方法需要實現(xiàn),GetFormat返回的是真正進行格式化操作的類,這里很像是工廠模式。
返回 MyHelloFormatter 對象之后,在MyHelloFormatter 中具體進行格式化操作。
?public?class?MyHelloFormatter?:?ICustomFormatter????{
????????#region?ICustomFormatter?Members
????????public?string?Format(string?format,?object?arg,?IFormatProvider?formatProvider)
????????{
????????????var?t?=?"Hello?";
????????????switch?(format)
????????????{
????????????????case?"UPP":
????????????????????t?=?t.ToUpper();
????????????????????break;
????????????????case?"LOW":
????????????????????t?=?t.ToLower();
????????????????????break;
????????????????default:
???????????????????break;
????????????}
????????????return?t?+?arg.ToString();
????????}
????????#endregion
????}
?
?
MyHelloFormatter?實現(xiàn)了ICustomFormatter接口,該接口也只有一個唯一的方法,即實際執(zhí)行格式化的方法
如果不使用格式化參數(shù)或者格式化參數(shù)不匹配,情況會怎么樣?
?
代碼 var?msg15?=?string.Format(new?MyHelloFormatProvider(),?"{0}??{1}",?new?Rectangle()?{?Name?=?"MyRectangle",?Width?=?14.3,?Height?=?10?},?new?Square()?{?Name?=?"MySquare",?Side?=?24.2?});????????????Console.WriteLine(msg15);
????????????var?msg16?=?string.Format(new?MyHelloFormatProvider(),?"{0}??{1}",?new?Rectangle()?{?Name?=?"MyRectangle",?Width?=?14.3,?Height?=?10?}.ToString(),?new?Square()?{?Name?=?"MySquare",?Side?=?24.2?}.ToString());
????????????Console.WriteLine(msg16);
????????????var?msg17?=?string.Format(new?MyHelloFormatProvider(),?"{0:AAA}??{1:BBB}",?new?Rectangle()?{?Name?=?"MyRectangle",?Width?=?14.3,?Height?=?10?},?new?Square()?{?Name?=?"MySquare",?Side?=?24.2?});
????????????Console.WriteLine(msg17);
?
以上輸出都是一樣的: Hello MyRectangle(Width:14.3,Height:10)? Hello MySquare(Side:24.2)?
上面的運行結(jié)果表明,如果提供了new?MyHelloFormatProvider() ,那么執(zhí)行過程過是: 根據(jù)MyHelloFormatProvider 對象得到 MyHelloFormatter?對象,利用MyHelloFormatter?對象的Format方法進行格式化
這里還有一個問題,如果 MyHelloFormatProvider?的 GetFormat返回的不是一個實現(xiàn)了 ICustomFormatter 接口的對象又會是什么情況呢?
答案是會報異常。 那么如果返回的是 null 呢? 答案是直接調(diào)用了對象的ToString()方法了。如果返回null,則運行結(jié)果如下:
MyRectangle(Width:14.3,Height:10)? MySquare(Side:24.2)?
?
帶上格式化參數(shù)的運行結(jié)果
?
var?msg18?=?string.Format(new?MyHelloFormatProvider(),?"{0:UPP}??{1:LOW}",?new?Rectangle()?{?Name?=?"MyRectangle",?Width?=?14.3,?Height?=?10?},?new?Square()?{?Name?=?"MySquare",?Side?=?24.2?});????????????Console.WriteLine(msg18);
?
?HELLO MyRectangle(Width:14.3,Height:10)? hello MySquare(Side:24.2)
?通過上面的例子我們知道如果我們需要定義一種通用的格式化方式的話,不需要讓類實現(xiàn) IFormattable 接口,可以通過定義實現(xiàn) IFormatProvider,ICustomFormatter接口的類去做,上面的無論是正方形還是長方形類都需要在前面加上 Hello 進行格式化,可以是普通的,小寫的,大寫的等等,不需要兩個類單獨去實現(xiàn)了,就選以后增加了圓形,三角形等等,也都能用我們已經(jīng)定義好的 MyHelloFormatProvider 和 MyHelloFormatter? 去進行格式化。
?
?
使用這種方式還能解決另外一個問題,假如我們已經(jīng)為圓形類實現(xiàn)了 IFormattable? 接口,并且已經(jīng)實現(xiàn)了{0:UPP}格式化參數(shù),但是實現(xiàn)的方法中沒有加{0:LOW}格式化參數(shù),而且這個類我們又不能更改(可能是.net自帶的類,可能是第三方dll提供的類等等),那該怎么辦呢? 顯然已經(jīng)不可能靠IFormattable? 接口來解決了
使用這節(jié)講的方法就可以實現(xiàn)我們要求了。以下是具體實現(xiàn)
圓形類 public?class?Circle?:?IFormattable????{
????????public?string?Name?{?get;?set;?}
????????///?<summary>
????????///?半徑
????????///?</summary>
????????public?double?Radius?{?get;?set;?}
????????public?override?string?ToString()
????????{
????????????return?string.Format("{0}(Radius:{1})",?Name,?Radius);
????????}
????????#region?IFormattable?Members
????????public?string?ToString(string?format,?IFormatProvider?formatProvider)
????????{
????????????if?(string.IsNullOrEmpty(format))
????????????????return?ToString();
????????????var?t?=?"Hello?";
????????????switch?(format)
????????????{
????????????????case?"UPP":
????????????????????t?=?t.ToUpper();
????????????????????break;
????????????????default:
????????????????????break;
????????????}
????????????return?t?+?Name;
????????}
????????#endregion
????}
?
該類可以實現(xiàn)UPP格式化參數(shù)的格式化。
?var?msg19?=?string.Format("Test:?{0}",?new?Circle()?{?Name?=?"MyCircle",?Radius?=?10?});????????????Console.WriteLine(msg19);
????????????var?msg20?=?string.Format("Test:?{0:UPP}",?new?Circle()?{Name?=?"MyCircle",?Radius?=?10});
????????????Console.WriteLine(msg20);
?
運行上面的代碼得到:
Test: MyCircle(Radius:10)
Test: HELLO MyCircle
第一個無格式化參數(shù),實際調(diào)用ToString()方法得到,由代碼?if?(string.IsNullOrEmpty(format))決定
第二個帶UPP格式化參數(shù),也得到了預(yù)期的結(jié)果。
?
現(xiàn)在需要實現(xiàn)LOW的格式化參數(shù)
var?msg21?=?string.Format(new?MyHelloFormatProvider(),"Test:?{0:LOW}",?new?Circle()?{?Name?=?"MyCircle",?Radius?=?10?});????????????Console.WriteLine(msg21);?
?
在不修改Circle類并且不重新定義其他類的情況下就可以達到我們的要求了
顯示結(jié)果如下: Test: hello MyCircle(Radius:10) hello已經(jīng)是全部小寫了。
轉(zhuǎn)載于:https://www.cnblogs.com/JhoneLee/archive/2013/05/08/3066950.html
總結(jié)
以上是生活随笔為你收集整理的(转)彻底学通string.Format以及IFormattable,IFormatProvider,ICustomFormatter的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JNI学习开始篇 基础知识 数据映射及学
- 下一篇: 生活在地球上:网络摘录