基础class、派生class、我class?

Base class, derived class, and Iclass?

我试图弄清楚一位更高级的开发人员编写的这段代码是如何工作的,但我感到非常困惑。我希望有人能帮助我理解这个项目中存在的所有不同 classes 和接口。

我有一个外部 class 存在于 class 库 dll 中,与我正在编辑的 Web 应用程序分开。我们称它为 outsideClass。我不明白为什么这个 class 有这么多的构造函数,甚至是如何发出请求的,因为只有一个构造函数有默认的 WebRequestHandler 而其余的没有。在 Web 应用程序中,此 class 的实例在 WebRequestHandler 为 null 的情况下进行初始化!!这快把我逼疯了……这是 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);
}

这是让我感到困惑的代码,在网络应用程序中发现,有一个 IConnClient 和一个 ConnClient class(为什么?)它们的定义如下:

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 服务调用?任何人都可以帮助解释所有这些 class 和接口之间的联系吗?这是非常压倒性的,很难弄清楚这里发生了什么......

我认为构造函数链的解释将有助于您在这里的理解。

public outsideClass(params DelegatingHandler[] handlers) : this(null, handlers) { }

这是 outsideClass 的第一个构造函数。注意它没有主体,但更重要的是这部分:this(null, handlers)。构造函数的这一部分说 'First, call my OTHER constructor that takes two parameters, then do some extra stuff'。然而,在这种情况下,没有额外的东西。

所以

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) { }

最终调用具有所有逻辑的 'main' 构造函数。

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 class 中的另一个构造函数,而是调用 BASE class 中的构造函数。

所以,

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) { }

它再次调用你的 'main' 构造函数!

public outsideClass(string aUser, string aPass, Uri aBaseAddress, WebRequestHandler innerHandler, params DelegatingHandler[] handlers){ ... }

最后,回答你关于接口的问题。在代码中使用它们有很多原因。一些最大的好处是它们将您的 class 的任何消费者与实际的 class 分离。这意味着,如果您在将来的某个时候更改实现,您可以避免重新处理所有消费者以及代码本身。

想一个例子,你正在编写一个库来处理数据库。你有一个 class MySqlDatabaseAdapater。如果您所有的代码都使用 MySqlDatabaseAdapter,并且您决定更改为 OracleDatabaseAdapter,则必须使用 MySqlDatabaseAdapter 更改所有内容!但是,如果用 MySqlDatabaseAdapter 实现一个接口,IDatabaseAdapter 和所有使用代码引用 IDatabaseAdapter 来安排你的代码,现在你可以用 OracleDatabaseAdapater 替换你的消费者,它也实现了IDatabaseAdapter 而且您需要更改的代码少得多!

另一个好处是测试代码。如果你有一个做网络 activity 的 class(这种看起来像)并且想对使用这个 class 的代码进行单元测试,那么你的单元测试将做网络 activity。不好! 但是,如果你使用接口方法,那么你可以设置你的测试来为你正在测试的代码提供一个 'fake' IConnClient 假装命中网络,但实际上并没有。这使您可以编写可靠、可预测且快速的测试。

希望对您有所帮助!

这不完全是一个答案,但我不能在评论中添加格式化代码。

这就是为什么 outsideClass 即使 innerHandler 在构造函数中为 null 也可以工作的原因:

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 container"。)这意味着该项目可能正在使用 DI 容器,如 Unity、Windsor、 Autofac,或类似的东西。您可能会在项目中看到对其中一个的引用。如果是这样,那就可以解释接口的特殊用途。典型的模式是

  • 定义接口
  • 定义一个实现接口
  • 的class
  • 使其他 classes 依赖于接口(不是 class)
  • 配置 DI 容器,这样如果 class 依赖于接口,容器会创建 class.
  • 的实例

这不是对依赖注入的很好解释,但如果这个猜测是正确的,那么它可能会帮助您找到一些代码来指示为所有这些构造函数提供的值。