[英]Base class, derived class, and Iclass?
我試圖弄清楚一位高級開發人員編寫的代碼是如何工作的,我感到非常困惑。 我希望有人可以幫助我理解該項目中存在的所有不同類和接口。
我的類庫dll中存在一個外部類,與我正在編輯的Web應用程序分開。 我們稱它為outsideClass。 我無法弄清楚為什么這個類有那么多的構造函數,以及如何發出請求,因為只有一個構造函數具有默認的WebRequestHandler,其余的則沒有。 在Web應用程序中,此類的實例被初始化為WebRequestHandler為空! 這讓我發瘋了...這是outsideClass:
public class outsideClass : webServiceInterface
{
private HttpClient _client = null;
private const int _TIMEOUT = 50;
private const string _assemblyName = "my.assembly.name";
private const string _resourcesName = "my.assembly.name.Properties.Resources";
private const string _certName = "client_cert_name";
private static WebRequestHandler CreateDefaultMessageHandler()
{
// ProgressMessageHandler prog = new ProgressMessageHandler();
//HttpClientHandler handler = new HttpClientHandler();
WebRequestHandler handler = new WebRequestHandler();
// The existing HttpClientHandler.SupportsProxy property indicates whether both the UseProxy property and the Proxy property are supported.
// This created an issue because some platforms(for example, Windows Phone) don’t allow you to configure a proxy explicitly.
// However, Windows Phone lets you control whether the machine wide proxy should be used.
// To model this behavior, we added the HttpClientHandler.SupportsUseProxy() extension method.
// For some platforms that don’t support both, such as Windows Phone, the SupportsProxy property will continue to return false, but the SupportsUseProxy() method will return true.
if (handler.SupportsProxy) // false on WinPhone
{
handler.Proxy = CrossWebProxy.SystemWebProxy;
handler.UseProxy = true;
}
// HttpClientHandler.SupportsAllowAutoRedirect(): The HttpClientHandler.SupportsRedirectConfiguration property had a similar issue:
// It controls whether both the AllowAutoRedirect and the MaxAutomaticRedirections properties are supported.
// Windows Phone doesn’t support specifying the maximum number of automatic redirections, but it does support redirection.
// For that reason, we added the HttpClientHandler.SupportsAllowAutoRedirect() extension method.
/*
if (handler.SupportsRedirectConfiguration) // false on WinPhone
{
handler.MaxAutomaticRedirections = 5;
} */
if (handler.SupportsRedirectConfiguration)
{
handler.AllowAutoRedirect = true;
}
X509Certificate2 certificate = getClientCertificate();
handler.ClientCertificates.Add(certificate);
return handler;
}
private static X509Certificate2 getClientCertificate()
{
System.Reflection.Assembly myAssembly;
myAssembly = System.Reflection.Assembly.Load(_assemblyName);
ResourceManager myManager = new ResourceManager(_resourcesName, myAssembly);
byte[] cert_file_bytes;
cert_file_bytes = (byte[])myManager.GetObject(_certName);
//string[] names = myAssembly.GetManifestResourceNames();
//using (Stream cert_file_stream = myAssembly.GetManifestResourceStream(names[0]))
//using (var streamReader = new MemoryStream())
//{
// cert_file_stream.CopyTo(streamReader);
// cert_file_bytes = streamReader.ToArray();
//}
X509Certificate2 cert_file = new X509Certificate2(cert_file_bytes, "server");
return cert_file;
}
public outsideClass(params DelegatingHandler[] handlers) : this(null, handlers)
{
}
public outsideClass(WebRequestHandler innerHandler, params DelegatingHandler[] handlers) : this("", "", innerHandler, handlers)
{
}
public outsideClass(string aUser, string aPass, params DelegatingHandler[] handlers) : this(aUser, aPass, null, null, handlers)
{
}
public outsideClass(string aUser, string aPass, Uri aBaseAddress, params DelegatingHandler[] handlers) : this(aUser, aPass, null, null, handlers)
{
}
public outsideClass(string aUser, string aPass, WebRequestHandler innerHandler, params DelegatingHandler[] handlers) : this(aUser, aPass, null, innerHandler, handlers)
{
}
public outsideClass(string aUser, string aPass, Uri aBaseAddress, WebRequestHandler innerHandler, params DelegatingHandler[] handlers)
{
if (innerHandler == null)
{
innerHandler = CreateDefaultMessageHandler();
}
if (handlers != null)
{
DelegatingHandler excHandler = handlers.FirstOrDefault(t => t is WebServiceExceptionsHandler);
if (excHandler == null)
{
// Add the custom handler to the list
// So this Client will raise only exception of type WebServiceException / WebServiceOfflineException / WebServiceTimedOutException / WebServiceStatusException
excHandler = new WebServiceExceptionsHandler();
IList<DelegatingHandler> list = handlers.ToList();
list.Insert(0, excHandler);
handlers = list.ToArray();
}
}
_client = HttpClientFactory.Create(innerHandler, handlers);
Timeout = TimeSpan.FromSeconds(_TIMEOUT);
SetCredentials(aUser, aPass);
BaseAddress = aBaseAddress;
}
protected long MaxResponseContentBufferSize
{
get { return _client.MaxResponseContentBufferSize; }
set { _client.MaxResponseContentBufferSize = value; }
}
protected HttpRequestHeaders DefaultRequestHeaders { get { return _client.DefaultRequestHeaders; } }
public Uri BaseAddress
{
get { return _client.BaseAddress; }
set { _client.BaseAddress = value; }
}
public TimeSpan Timeout
{
get { return _client.Timeout; }
set { _client.Timeout = value; }
}
public string Id { get; private set; }
public string Key { get; private set; }
/// <summary>
/// Set Basic Authentication Header
/// </summary>
public void SetCredentials(string aApplicationId, string aApplicationKey)
{
Id = aApplicationId;
Key = aApplicationKey;
_client.DefaultRequestHeaders.Authorization = null;
if (string.IsNullOrEmpty(Id) == true ||
string.IsNullOrEmpty(Key) == true)
{
return;
}
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.UTF8Encoding.UTF8.GetBytes(string.Format("{0}:{1}",
this.Id, this.Key))));
}
protected async Task<T> SendAndReadAsAsync<T>(HttpRequestMessage aRequest, MediaTypeFormatterCollection formatters, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsAsync<T>(formatters).ConfigureAwait(false);
}
protected async Task<T> SendAndReadAsAsync<T>(HttpRequestMessage aRequest, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsAsync<T>().ConfigureAwait(false);
}
protected async Task<string> SendAndReadAsStringAsync(HttpRequestMessage aRequest, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
protected async Task<Stream> SendAndReadAsStreamAsync(HttpRequestMessage aRequest, HttpCompletionOption aCompletionOption = HttpCompletionOption.ResponseHeadersRead, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCompletionOption, aCancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
}
protected async Task<byte[]> SendAndReadAsByteArrayAsync(HttpRequestMessage aRequest, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
}
protected async Task<HttpResponseMessage> SendAsync(HttpRequestMessage aRequest, CancellationToken aCancellationToken = default(CancellationToken))
{
HttpResponseMessage response = await _client.SendAsync(aRequest, aCancellationToken).ConfigureAwait(false);
return response;
}
}
webServiceInterface(為什么需要這樣做,目的是什么?):
public interface webServiceInterface
{
Uri BaseAddress { get; set; }
TimeSpan Timeout { get; set; }
string Id { get; }
string Key { get; }
void SetCredentials(string aId, string aKey);
}
這是在Web應用程序中發現的令人困惑的代碼,其中包含IConnClient和ConnClient類(為什么?),它們的定義如下:
public interface IConnClient : IClientWebService
{
Task UpdateSomethingAsync(Guid aCompanyId, Guid aUserId, Guid aAgronomistId, bool shareCN1, bool getRxMap, DateTime endDate, CancellationToken aCancelToken = default(CancellationToken));
Task<IList<SomethingDto>> GetSomethingListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken));
Task DeleteSomethingAsync(Guid aCompanyId, Guid aAgronomistId, CancellationToken aCancelToken = default(CancellationToken));
Task<IList<StuffDto>> GetStuffListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken));
Task<IList<StuffDto>> GetStuffListAsync(Guid aCompanyId, Guid aAgronomistId, CancellationToken aCancelToken = default(CancellationToken));
Task<IList<StuffDto>> GetStuffListForThingsAsync(Guid aCompanyId, Guid aThingsId, CancellationToken aCancelToken = default(CancellationToken));
Task<IList<ThingsDetailsDto>> GetThingsListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken));
Task<string> SendStuffListToThingsListAsync(Guid aCompanyId, Guid aUserId, IList<StuffDto> aStuffList, IList<Guid> aThingsList, CancellationToken aCancelToken = default(CancellationToken));
// Task<bool> GetStuffStatusAsync(Guid aCompanyId, string aStuffId, CancellationToken aCancelToken = default(CancellationToken));
}
public class ConnClient : outsideClass, IConnClient
{
private const string _SHARE_CN1 = "Share-CN1";
private const string _GET_RXMAP = "Get-RxMap";
private DateTime Epoch2DateTime(long epoch)
{
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(epoch);
}
private long DateTime2Epoch(DateTime datetime)
{
return (long)datetime.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
}
public ConnClient(Uri aBaseAddress, string apiKey, WebRequestHandler innerHandler = null, params DelegatingHandler[] handlers)
: base("", "", innerHandler, handlers) // DIP - IoC
{
DefaultRequestHeaders.Authorization = null; // TODO: Add authorization?
DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
BaseAddress = aBaseAddress;
}
public async Task UpdateSomethingAsync(Guid aCompanyId, Guid aUserId, Guid aAgronomistId, bool shareCN1, bool getRxMap, DateTime endDate, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(UpdateSomethingAsync) + " Company GUID is Empty");
if (aUserId == Guid.Empty)
throw new ArgumentException(nameof(UpdateSomethingAsync) + " User GUID is Empty");
List<string> permissions = new List<string>();
if (shareCN1)
{
permissions.Add(_SHARE_CN1);
}
if (getRxMap)
{
permissions.Add(_GET_RXMAP);
}
UpdateSomethingRequestDto reqDto = new UpdateSomethingRequestDto();
reqDto.Somethings = new Somethings();
reqDto.Somethings.userId = aUserId;
reqDto.Somethings.thirdparty = aAgronomistId;
if (endDate.Year != 1)
reqDto.Somethings.endDate = DateTime2Epoch(endDate);
else
reqDto.Somethings.endDate = 0;
reqDto.Somethings.permissions = permissions;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, "companies/" + aCompanyId + "/Somethings");
JsonMediaTypeFormatter fmt = new JsonMediaTypeFormatter();
request.Content = new ObjectContent(reqDto.GetType(), reqDto, fmt, "application/json");
var res = await SendAndReadAsStringAsync(request, aCancelToken);
}
public async Task<IList<SomethingDto>> GetSomethingListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(GetSomethingListAsync) + " Company GUID is Empty");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "companies/" + aCompanyId + "/Somethings");
GetSomethingsResponseDto res = await SendAndReadAsAsync<GetSomethingsResponseDto>(request, aCancelToken);
return res?.Somethings;
}
public async Task DeleteSomethingAsync(Guid aCompanyId, Guid aAgronomistId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(DeleteSomethingAsync) + " Company GUID is Empty");
if (aAgronomistId == Guid.Empty)
throw new ArgumentException(nameof(DeleteSomethingAsync) + " Agronomist GUID is Empty");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, "companies/" + aCompanyId + "/Somethings?thirdPartyId=" + aAgronomistId);
var res = await SendAndReadAsStringAsync(request, aCancelToken);
}
public async Task<IList<StuffDto>> GetStuffListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(GetStuffListAsync) + " Company GUID is Empty");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "companies/" + aCompanyId + "/Stuffs");
GetStuffsResponseDto res = await SendAndReadAsAsync<GetStuffsResponseDto>(request, aCancelToken);
return res?.Stuffs;
}
public async Task<IList<StuffDto>> GetStuffListAsync(Guid aCompanyId, Guid aAgronomistId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(GetStuffListAsync) + " Company GUID is Empty");
if (aAgronomistId == Guid.Empty)
return await GetStuffListAsync(aCompanyId);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "companies/" + aCompanyId + "/Stuffs?thirdPartyId=" + aAgronomistId);
GetStuffsResponseDto res = await SendAndReadAsAsync<GetStuffsResponseDto>(request, aCancelToken);
return res?.Stuffs;
}
public async Task<IList<ThingsDetailsDto>> GetThingsListAsync(Guid aCompanyId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(GetThingsListAsync) + " Company GUID is Empty");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "vehicles/?companyId=" + aCompanyId);
GetVehiclesResponseDto res = await SendAndReadAsAsync<GetVehiclesResponseDto>(request, aCancelToken);
return res?.vehicles;
}
private SendStuffsRequestDto CreateSendStuffDto(Guid aUserId, IList<StuffDto> aStuffList, IList<Guid> aVehicleList)
{
SendStuffsRequestDto res = new SendStuffsRequestDto();
res.StuffData = new List<StuffDataDto>();
res.userId = aUserId;
foreach (StuffDto prescr in aStuffList)
{
StuffDataDto data = new StuffDataDto();
data.StuffID = prescr.StuffId;
data.vehicles = aVehicleList;
res.StuffData.Add(data);
}
return res;
/*
SendStuffsRequestDto res = new SendStuffsRequestDto();
res.StuffData = new List<StuffDataDto>();
res.userId = aUserId;
foreach (StuffDto prescr in aStuffList)
{
IList<Guid> prescrVehicleList = prescr.vehicleDetails.Select(l => l.vehicleId).ToList();
IList<Guid> joinedList = aVehicleList.Where(c => prescrVehicleList.Contains(c)).ToList();
if (joinedList == null || joinedList.Count == 0)
continue;
StuffDataDto data = new StuffDataDto();
data.StuffID = prescr.StuffId;
data.vehicles = new List<Guid>();
data.vehicles = joinedList;
res.StuffData.Add(data);
}
return res;*/
}
public async Task<string> SendStuffListToThingsListAsync(Guid aCompanyId, Guid aUserId, IList<StuffDto> aStuffList, IList<Guid> aVehicleList, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(SendStuffListToThingsListAsync) + " Company GUID is Empty");
if (aUserId == Guid.Empty)
throw new ArgumentException(nameof(SendStuffListToThingsListAsync) + " User GUID is Empty");
SendStuffsRequestDto reqDto = CreateSendStuffDto(aUserId, aStuffList, aVehicleList);
if (reqDto.StuffData.Count == 0)
return "";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "companies/" + aCompanyId + "/Stuffs/send");
JsonMediaTypeFormatter fmt = new JsonMediaTypeFormatter();
request.Content = new ObjectContent(reqDto.GetType(), reqDto, fmt, "application/json");
return await SendAndReadAsStringAsync(request, aCancelToken);
}
public async Task<IList<StuffDto>> GetStuffListForThingsAsync(Guid aCompanyId, Guid aVehicleId, CancellationToken aCancelToken = default(CancellationToken))
{
if (aCompanyId == Guid.Empty)
throw new ArgumentException(nameof(GetStuffListForThingsAsync) + " Company GUID is Empty");
if (aVehicleId == Guid.Empty)
throw new ArgumentException(nameof(GetStuffListForThingsAsync) + " Vehicle GUID is Empty");
IList<StuffDto> allPresc = await GetStuffListAsync(aCompanyId, aCancelToken);
List<StuffDto> res = new List<StuffDto>();
foreach (StuffDto prescr in allPresc)
{
ThingsDetailsDto vehicle = prescr.vehicleDetails.FirstOrDefault(t => t.vehicleId == aVehicleId);
if (vehicle != null)
{
// Clones the Stuff...
ISerializationInterface serializer = new SerializationJson();
string tmpString = serializer.Serialize(prescr);
StuffDto newPrescr = serializer.Deserialize<StuffDto>(tmpString);
// Remove all the vehicles
newPrescr.vehicleDetails.Clear();
// Add the vehicle found
newPrescr.vehicleDetails.Add(vehicle);
res.Add(newPrescr);
}
}
return res;
}
/*
public Task<bool> GetStuffStatusAsync(Guid aCompanyId, string aStuffId, CancellationToken aCancelToken = default(CancellationToken))
{
// This API has been marked as Optional on PostMan...
throw new NotImplementedException();
} */
}
最后,這是在代碼中的用法:
IConnClient connClient = ConnClientFactory.Create();
connClient.Timeout = TimeSpan.FromMinutes(4);
//Then later in the code:
connClient.GetStuffListAsync(companyGuid);
我非常困惑的部分是connClient中的這一部分:
public ConnClient(Uri aBaseAddress, string apiKey, WebRequestHandler innerHandler = null, params DelegatingHandler[] handlers)
: base("", "", innerHandler, handlers) // DIP - IoC
看起來他們使用的是構造函數,其中innerHandler(WebRequestHandler)未使用默認值初始化,因此為null,那么如何進行Web服務調用? 誰能幫助解釋所有這些類和接口之間的連接? 這是極其壓倒性的,很難弄清這里正在發生什么...
我認為對解釋器鏈的解釋將在這里幫助您更好地理解。
public outsideClass(params DelegatingHandler[] handlers) : this(null, handlers) { }
這是outsideClass
的第一個構造函數。 請注意,它沒有主體,但更重要的是這一部分: this(null, handlers)
。 構造函數的這一部分說:“首先,調用具有兩個參數的OTHER構造函數,然后再做一些額外的工作”。 但是,在這種情況下,沒有多余的東西。
所以
public outsideClass(params DelegatingHandler[] handlers) : this(null, handlers) { }
電話
public outsideClass(WebRequestHandler innerHandler, params DelegatingHandler[] handlers) : this("", "", innerHandler, handlers) { }
哪個電話
public outsideClass(string aUser, string aPass, WebRequestHandler innerHandler, params DelegatingHandler[] handlers) : this(aUser, aPass, null, innerHandler, handlers) { }
最終調用具有所有邏輯的“主”構造函數。
public outsideClass(string aUser, string aPass, Uri aBaseAddress, WebRequestHandler innerHandler, params DelegatingHandler[] handlers){ ... }
之所以這樣構造構造函數,是為了減少代碼重復,並確保如果必須更改構造函數邏輯,則可以成功更改它們。
接下來,讓我們看看ConnClient
。 它的構造函數看起來像
public ConnClient(Uri aBaseAddress, string apiKey, WebRequestHandler innerHandler = null, params DelegatingHandler[] handlers)
: base("", "", innerHandler, handlers) // DIP - IoC
與outsideClass
相似,但是它具有this(...)
,而不是base("", "", innerHandler, handlers)
。 base
函數非常像this
,但是它沒有在SAME類中調用另一個構造函數,而是在BASE類中調用了一個構造函數。
所以,
public ConnClient(Uri aBaseAddress, string apiKey, WebRequestHandler innerHandler = null, params DelegatingHandler[] handlers)
: base("", "", innerHandler, handlers) // DIP - IoC
電話
public outsideClass(string aUser, string aPass, WebRequestHandler innerHandler, params DelegatingHandler[] handlers) : this(aUser, aPass, null, innerHandler, handlers) { }
再次調用您的“主要”構造函數!
public outsideClass(string aUser, string aPass, Uri aBaseAddress, WebRequestHandler innerHandler, params DelegatingHandler[] handlers){ ... }
最后,回答關於接口的問題。 在代碼中使用它們的原因有很多。 最大的好處是,它們可以使您的所有此類消費者與實際的消費者脫鈎。 這意味着,如果將來要更改實現,則可以避免重新設計所有使用者以及代碼本身的麻煩。
想想一個示例,您正在編寫一個庫來處理數據庫。 您有一個MySqlDatabaseAdapater
類。 如果所有代碼都使用MySqlDatabaseAdapter
,並且您決定更改為OracleDatabaseAdapter
,則必須使用MySqlDatabaseAdapter
進行所有更改! 但是,如果用實現接口的MySqlDatabaseAdapter
安排代碼,則IDatabaseAdapter
和所有使用的代碼都引用IDatabaseAdapter
,現在您可以用OracleDatabaseAdapater
替換使用者,ALSO也實現IDatabaseAdapter
,您需要更改的代碼少得多!
另一個好處是測試代碼。 如果您有一個進行網絡活動的類(類似於此類),並且想要對使用該類的代碼進行單元測試,則您的單元測試將進行網絡活動。 不好! 但是,如果您使用接口方法,則可以設置測試,以為要測試的代碼提供一個假冒的IConnClient
,該假冒產品可假冒網絡,但實際上並非如此。 這使您可以編寫可靠,可預測且快速的測試。
希望這可以幫助!
這不是一個完整的答案,但是我不能在注釋中放入格式化的代碼。
這就是為什么即使在構造函數中innerHandler
為null, outsideClass
也可以工作的原因:
if (innerHandler == null)
{
innerHandler = CreateDefaultMessageHandler();
}
另外,此行末尾的注釋可能會揭示以下內容:
public ConnClient(Uri aBaseAddress, string apiKey, WebRequestHandler innerHandler = null, params DelegatingHandler[] handlers)
: base("", "", innerHandler, handlers) // DIP - IoC
我懷疑DIP - IoC
可能是對依賴項注入容器(有時也稱為“ IoC容器”)的引用。這意味着項目可能正在使用DI容器,例如Unity,Windsor,Autofac或類似的東西。 您可能會在項目中看到對其中之一的引用。 如果是這樣,那可能可以解釋接口的特殊用法。 典型的模式是
那不是對依賴注入的一個很好的解釋,但是如果這種猜測是正確的,那么它可能會幫助您找到一些代碼,這些代碼指示向所有這些構造函數提供什么值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.