数据协定继承

已知类型和泛型解析程序

Juval Lowy

下载代码示例

自首次发布以来,Windows Communication Foundation (WCF) 开发人员便必须处理数据协定继承方面的麻烦(一个称为“已知类型”的问题)。 在本文中,我首先说明这个问题的来源,讨论 Microsoft .NET Framework 3.0 和 .NET Framework 4 中的可用缓解方法,然后演示我用来完全解决这个问题的方法。 除此之外,还会介绍一些高级 WCF 编程技术。

按值与 按引用

在传统的面向对象语言(如 C++ 和 C#)中,派生类维护与其基类之间的包含 关系。 也就是说,进行如下声明后,每个 B 对象仍然是 A 对象:

 
class A {...}
class B : A {...}

如果以图形表示,这类似于图 1 中的维恩图,每个 B 实例仍然是 A 实例(但不一定每个 A 都是 B)。

图 1 包含关系

从传统的面向对象域建模角度,包含关系可用于针对基类设计代码,同时与子类进行交互。这意味着可以逐步发展域实体的建模,同时尽量减小对应用程序的影响。

例如,考虑一个业务联系人管理应用程序,按这种建模方式声明一个基类 Contact 和一个派生类 Customer,派生类通过添加客户的特性来细化联系人:

 
class Contact {
public string FirstName;
public string LastName;
} class Customer : Contact {
public int OrderNumber;
}

应用程序中原为 Contact 类型编写的所有方法都可以接受 Customer 对象,如图 2 所示。

图 2 交换基类和子类引用

 
interface IContactManager {
void AddContact(Contact contact);
Contact[] GetContacts();
} class AddressBook : IContactManager {
public void AddContact(Contact contact)
{...}
...
} IContactManager contacts = new AddressBook(); Contact contact1 = new Contact();
Contact contact2 = new Customer();
Customer customer = new Customer(); contacts.AddContact(contact1);
contacts.AddContact(contact2);
contacts.AddContact(customer);

图 2 中的代码能够正常运行,是因为编译器在内存中表示对象状态的方式。为了支持基类与其子类之间的包含关系,在分配新子类实例时,编译器首先分配对象状态的基类部分,然后直接在其后追加子类部分,如图 3 所示。

图 3 内存中的对象状态层次结构

对于需要引用 Contact 的方法,如果实际提供的是对 Customer 的引用,该方法仍可正常运行,这是因为 Customer 引用也是对 Contact 的引用。

很遗憾,在 WCF 中,不能采用这种复杂的方式。与传统的面向对象或经典的 CLR 编程模型不同,WCF 按值传递所有操作参数,而不是按引用。即使代码类似于按引用传递参数(如在常规 C# 中),WCF 代理实际上是将参数序列化到消息中。参数在 WCF 消息中打包并传输给服务,然后在服务中反序列化为本地引用,供服务操作使用。

服务操作向客户端返回结果时,也会进行这一系列操作:结果(或传出参数或异常)首先序列化到回复消息中,然后在客户端重新反序列化。

序列化的确切形式通常取决于编写服务协定所依据的数据协定。例如,考虑以下数据协定:

 
[DataContract]
class Contact {...} [DataContract]
class Customer : Contact {...}

使用这些数据协定可以定义以下服务协定:

 
[ServiceContract]
interface IContactManager {
[OperationContract]
void AddContact(Contact contact); [OperationContract]
Contact[] GetContacts();
}

对于多层应用程序,按值封送参数优于按引用封送,因为体系结构中的任何层都可自由地向数据协定后的行为提供自己的解释。 按值封送还可实现远程调用、互操作性、排队调用和长时间运行的工作流。

但是与传统的面向对象不同,针对 Contact 类编写的服务操作默认情况下无法使用 Customer 子类。 原因很简单:如果确实向需要基类引用的服务操作传递了子类引用,WCF 如何知道要将派生类部分序列化到消息中呢?

因此,根据定义,下面的 WCF 代码会失败:

 
class ContactManagerClient : ClientBase<IContactManager> :
IContactManager{
...
} IContactManager proxy = new ContactManagerClient();
Contact contact = new Customer(); // This will fail:
contacts.AddContact(contact);

已知类型支持

对于 .NET Framework 3.0,WCF 能够使用 KnownTypeAttribute 解决将基类引用替换为子类引用的问题,KnownTypeAttribute 定义为:

 
[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,
AllowMultiple = true)]
public sealed class KnownTypeAttribute : Attribute {
public KnownTypeAttribute(Type type);
//More members
}

通过 KnownType 特性可以为数据协定指定可接受的子类:

 
[DataContract]
[KnownType(typeof(Customer))]
class Contact {...} [DataContract]
class Customer : Contact {...}

当客户端传递使用已知类型声明的数据协定时,WCF 消息格式化程序会测试类型(类似使用 is 运算符)并查看其是否为所需已知类型。 如果是,则将参数序列化为子类而不是基类。

KnownType 特性对所有服务和终结点中所有使用基类的协定和操作产生影响,因此可接受子类而不是基类。此外,它还在元数据中包含子类,以便客户端拥有自己的子类定义并能够传递子类而不是基类。

如果需要多个子类,开发人员必须列出所有子类:

 
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...} [DataContract]
class Person : Contact {...}

WCF 格式化程序使用反射收集数据协定的所有已知类型,然后检查提供的参数是否为任何已知类型。

请注意,必须在数据协定类层次结构中显式添加所有级别。 添加子类不会添加其基类:

 
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...} [DataContract]
class Customer : Contact {...} [DataContract]
class Person : Customer {...}

KnownType 特性可能涉及范围太大,因此,WCF 还提供了 ServiceKnownTypeAttribute,可应用于特定操作或特定协定。

最后,在 .NET Framework 3.0 中,WCF 还允许在应用程序配置文件的 system.runtime.serialization 节中列出所需已知类型。

尽管从技术上来讲,使用已知类型可以解决问题,您可能还是会感觉到些许担心。 在传统的面向对象建模中,从不需要将基类与任何特定子类耦合。 准确地说,好基类的特点在于:好基类是所有可能子类的良好基础,但应用已知类型只能够解决已知的子类。 如果在设计系统时提前进行所有建模工作,这可能不是问题。实际上,随着应用程序建模的逐步进行,可能遇到目前未知的类型,这样,至少必须重新部署应用程序,更可能还要修改基类。

数据协定解析程序

若要缓解这个问题,.NET Framework 4 WCF 引入了一种在运行时解析已知类型的方式。 这种编程方法(称为数据协定解析程序)是功能最强的方法,这样可以通过扩展完全自动处理已知类型问题。 实际上,这样可以截获操作序列化和反序列化参数的尝试,在客户端和服务端都在运行时解析已知类型。

实现编程解析的第一步是从抽象类 DataContractResolver 派生,DataContractResolver 定义为:

 
public abstract class DataContractResolver {
protected DataContractResolver(); public abstract bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace); public abstract Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver);
}

TryResolveType 的实现会在 WCF 尝试将类型序列化到消息中时调用,提供的类型(type 参数)与操作协定中声明的类型(declaredType 参数)不同。 如果要序列化类型,需要提供一些唯一标识符以用作字典中的键,而字典将标识符映射到类型。 WCF 在反序列化过程中提供这些键,以便与类型进行绑定。

请注意,命名空间键不能为空字符串或 null。 尽管几乎任何唯一字符串值都可以作为标识符,我还是建议只使用 CLR 类型名称和命名空间。 将类型名称和命名空间设置为 typeName 和 typeNamespace 输出参数。

如果从 TryResolveType 返回 true,则将类型视为已解析,就像应用了 KnownType 特性一样。 如果返回 false,则 WCF 调用失败。 请注意,TryResolveType 必须解析所有已知类型,即使是使用 KnownType 特性修饰或在配置文件中列出的类型也是如此。 这样存在以下潜在风险:这需要解析程序与应用程序中的所有已知类型耦合,并且以后对其他类型的操作调用也会失败。 因此,最好是使用默认已知类型解析程序(未使用您的解析程序时,WCF 使用该解析程序)解析类型。 这正是 knownTypeResolver 参数的用途。 如果 TryResolveType 的实现无法解析类型,则应委托给 knownTypeResolver。

ResolveName 会在 WCF 尝试从消息反序列化出类型时调用,提供的类型(type 参数)与操作协定中声明的类型(declaredType 参数)不同。 这种情况下,WCF 提供类型名称和命名空间标识符,以便将其映射回已知类型。

例如,再次考虑这两个数据协定:

 
[DataContract]
class Contact {...} [DataContract]
class Customer : Contact {...}

图 4 列出了用于 Customer 类型的简单解析程序。

图 4 CustomerResolver

 
class CustomerResolver : DataContractResolver {
string Namespace {
get {
return typeof(Customer).Namespace ?? "
global";
}
} string Name {
get {
return typeof(Customer).Name;
}
} public override Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver) { if(typeName == Name && typeNamespace == Namespace) {
return typeof(Customer);
}
else {
return knownTypeResolver.ResolveName(
typeName,typeNamespace,declaredType,null);
}
} public override bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace) { if(type == typeof(Customer)) {
XmlDictionary dictionary = new XmlDictionary();
typeName = dictionary.Add(Name);
typeNamespace = dictionary.Add(Namespace);
return true;
}
else {
return knownTypeResolver.TryResolveType(
type,declaredType,null,out typeName,out typeNamespace);
}
}
}

该解析程序必须附加为代理或服务终结点上的每个操作的行为。 ServiceEndpoint 类有一个名为 Contract 且类型为 ContractDescription 的属性:

 
public class ServiceEndpoint {
public ContractDescription Contract
{get;set;} // More members
}

ContractDescription 有一个操作描述集合,协定上每个操作都有一个 OperationDescription 实例:

 
public class ContractDescription {
public OperationDescriptionCollection Operations
{get;} // More members
}
public class OperationDescriptionCollection :
Collection<OperationDescription>
{...}

每个 OperationDescription 都有一个类型为 IOperationBehavior 的操作行为集合:

 
public class OperationDescription {
public KeyedByTypeCollection<IOperationBehavior> Behaviors
{get;}
// More members
}

在其行为集合中,每个操作始终有一个名为 DataContractSerializerOperationBehavior,该行为有一个 DataContractResolver 属性:

 
public class DataContractSerializerOperationBehavior :
IOperationBehavior,...
{
public DataContractResolver DataContractResolver
{get;set}
// More members
}

DataContractResolver 属性的默认值为 null,不过,可以设置为自定义解析程序。 若要在主机端安装解析程序,必须循环访问主机维护的服务描述中的终结点集合:

 
public class ServiceHost : ServiceHostBase {...}

public abstract class ServiceHostBase : ...
{
public ServiceDescription Description
{get;}
// More members
} public class ServiceDescription {
public ServiceEndpointCollection Endpoints
{get;}
// More members
} public class ServiceEndpointCollection :
Collection<ServiceEndpoint> {...}

假设有以下服务定义并使用图 4 中的解析程序:

 
[ServiceContract]
interface IContactManager {
[OperationContract]
void AddContact(Contact contact);
...
}
class AddressBookService : IContactManager {...}

图 5 演示如何在 AddressBookService 的主机上安装解析程序。

图 5 在主机上安装解析程序

 
ServiceHost host =
new ServiceHost(typeof(AddressBookService)); foreach(ServiceEndpoint endpoint in
host.Description.Endpoints) {
foreach(OperationDescription operation in
endpoint.Contract.Operations) { DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new CustomerResolver();
}
}
host.Open();

在客户端,可按照类似步骤执行,只是需要在代理或通道工厂的单个终结点上设置解析程序。 例如,如果给定以下代理类定义:

 
class ContactManagerClient : ClientBase<IContactManager>,IContactManager
{...}

图 6 演示如何在代理上安装解析程序,以使用已知类型调用图 5 的服务。

图 6 在代理上安装解析程序

 
ContactManagerClient proxy = new ContactManagerClient();

foreach(OperationDescription operation in
proxy.Endpoint.Contract.Operations) { DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>(); behavior.DataContractResolver = new CustomerResolver();
} Customer customer = new Customer();
...
proxy.AddContact(customer);

泛型解析程序

为每个类型编写和安装解析程序显然工作量非常巨大,这需要细心跟踪所有已知类型(这样很容易出错,如果系统向前发展,很快会变得无法控制)。 为了自动实现解析程序,我编写了类 GenericResolver,其定义如下:

 
public class GenericResolver : DataContractResolver {
public Type[] KnownTypes
{get;} public GenericResolver();
public GenericResolver(Type[] typesToResolve); public static GenericResolver Merge(
GenericResolver resolver1,
GenericResolver resolver2);
}

GenericResolver 提供两个构造函数。 一个构造函数可以接受要解析的已知类型的数组。 这个无参数构造函数自动将调用程序集中的所有类和结构,以及调用程序集引用的程序集中的所有公共类和结构添加为已知类型。 它不会添加源自 .NET Framework 引用的程序集中的类型。

此外,GenericResolver 还提供了一个 Merge 静态方法,可用于合并两个解析程序的已知类型,返回可解析这两个解析程序并集的 GenericResolver。 图 7 演示 GenericResolver 的相关部分,不反映程序集中的类型(与 WCF 无关)。

图 7 实现 GenericResolver(部分)

 
public class GenericResolver : DataContractResolver {
const string DefaultNamespace = "global"; readonly Dictionary<Type,Tuple<string,string>> m_TypeToNames;
readonly Dictionary<string,Dictionary<string,Type>> m_NamesToType; public Type[] KnownTypes {
get {
return m_TypeToNames.Keys.ToArray();
}
} // Get all types in calling assembly and referenced assemblies
static Type[] ReflectTypes() {...} public GenericResolver() : this(ReflectTypes()) {} public GenericResolver(Type[] typesToResolve) {
m_TypeToNames = new Dictionary<Type,Tuple<string,string>>();
m_NamesToType = new Dictionary<string,Dictionary<string,Type>>(); foreach(Type type in typesToResolve) {
string typeNamespace = GetNamespace(type);
string typeName = GetName(type); m_TypeToNames[type] = new Tuple<string,string>(typeNamespace,typeName); if(m_NamesToType.ContainsKey(typeNamespace) == false) {
m_NamesToType[typeNamespace] = new Dictionary<string,Type>();
} m_NamesToType[typeNamespace][typeName] = type;
}
} static string GetNamespace(Type type) {
return type.Namespace ??
DefaultNamespace;
} static string GetName(Type type) {
return type.Name;
} public static GenericResolver Merge(
GenericResolver resolver1, GenericResolver resolver2) { if(resolver1 == null) {
return resolver2;
} if(resolver2 == null) {
return resolver1;
} List<Type> types = new List<Type>(); types.AddRange(resolver1.KnownTypes);
types.AddRange(resolver2.KnownTypes); return new GenericResolver(types.ToArray());
} public override Type ResolveName(
string typeName,string typeNamespace,
Type declaredType,
DataContractResolver knownTypeResolver) { if(m_NamesToType.ContainsKey(typeNamespace)) {
if(m_NamesToType[typeNamespace].ContainsKey(typeName)) {
return m_NamesToType[typeNamespace][typeName];
}
} return knownTypeResolver.ResolveName(
typeName,typeNamespace,declaredType,null);
} public override bool TryResolveType(
Type type,Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace) { if(m_TypeToNames.ContainsKey(type)) {
XmlDictionary dictionary = new XmlDictionary();
typeNamespace = dictionary.Add(m_TypeToNames[type].Item1);
typeName = dictionary.Add(m_TypeToNames[type].Item2);
return true;
}
else {
return knownTypeResolver.TryResolveType(
type,declaredType,null,out typeName,
out typeNamespace);
}
}
}

GenericResolver 最重要的成员是 m_TypeToNames 和 m_NamesToType 字典。 m_TypeToNames 将类型映射到其名称和命名空间的元组。 m_NamesToType 将类型命名空间和名称映射到实际类型。 以类型数组为参数的构造函数序列化这两个字典。 TryResolveType 方法使用提供的类型作为 m_TypeToNames 字典中的键,以读取类型的名称和命名空间。 ResolveName 方法使用提供的命名空间和名称作为 m_NamesToType 字典中的键,以返回解析后的类型。

尽管可以使用类似于图 5 和图 6 的冗长代码来安装 GenericResolver,最好还是使用扩展方法进行简化。 为此,可以使用 GenericResolverInstaller 的 AddGenericResolver 方法,这些方法定义如下:

 
public static class GenericResolverInstaller {
public static void AddGenericResolver(
this ServiceHost host, params Type[] typesToResolve); public static void AddGenericResolver<T>(
this ClientBase<T> proxy,
params Type[] typesToResolve) where T : class; public static void AddGenericResolver<T>(
this ChannelFactory<T> factory,
params Type[] typesToResolve) where T : class;
}

AddGenericResolver 方法接受类型的参数数组,也就是开放的、以逗号分隔的类型列表。 如果未指定类型,则 AddGenericResolver 将调用程序集中的所有类和结构,以及引用的程序集中的所有公共类和结构添加为已知类型。 例如,考虑以下已知类型:

 
[DataContract]
class Contact {...} [DataContract]
class Customer : Contact {...} [DataContract]
class Employee : Contact {...}

图 8 演示几个示例说明如何使用 AddGenericResolver 扩展方法处理这些类型。

图 8 安装 GenericResolver

 
// Host side

ServiceHost host1 = new ServiceHost(typeof(AddressBookService));
// Resolve all types in this and referenced assemblies
host1.AddGenericResolver();
host1.Open(); ServiceHost host2 = new ServiceHost(typeof(AddressBookService));
// Resolve only Customer and Employee
host2.AddGenericResolver(typeof(Customer),typeof(Employee));
host2.Open(); ServiceHost host3 = new ServiceHost(typeof(AddressBookService));
// Can call AddGenericResolver() multiple times
host3.AddGenericResolver(typeof(Customer));
host3.AddGenericResolver(typeof(Employee));
host3.Open(); // Client side ContactManagerClient proxy = new ContactManagerClient();
// Resolve all types in this and referenced assemblies
proxy.AddGenericResolver(); Customer customer = new Customer();
...
proxy.AddContact(customer);

GenericResolverInstaller 不仅安装 GenericResolver,还尝试将其与旧的泛型解析程序(如果存在)合并。这意味着,可以多次调用 AddGenericResolver 方法。 这在添加受限的泛型类型时十分方便:

 
[DataContract]
class Customer<T> : Contact {...} ServiceHost host = new ServiceHost(typeof(AddressBookService)); // Add all non-generic known types
host.AddGenericResolver(); // Add the generic types
host.AddGenericResolver(typeof(Customer<int>,Customer<string>)); host.Open();

图 9 演示 GenericResolverInstaller 的部分实现。

图 9 实现 GenericResolverInstaller

 
public static class GenericResolverInstaller {
public static void AddGenericResolver(
this ServiceHost host, params Type[] typesToResolve) { foreach(ServiceEndpoint endpoint in
host.Description.Endpoints) { AddGenericResolver(endpoint,typesToResolve);
}
} static void AddGenericResolver(
ServiceEndpoint endpoint,Type[] typesToResolve) { foreach(OperationDescription operation in
endpoint.Contract.Operations) { DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<
DataContractSerializerOperationBehavior>(); GenericResolver newResolver; if(typesToResolve == null ||
typesToResolve.Any() == false) { newResolver = new GenericResolver();
}
else {
newResolver = new GenericResolver(typesToResolve);
} GenericResolver oldResolver =
behavior.DataContractResolver as GenericResolver;
behavior.DataContractResolver =
GenericResolver.Merge(oldResolver,newResolver);
}
}
}

如果未提供类型,AddGenericResolver 会使用 GenericResolver 的无参数构造函数。 否则,它通过调用另一个构造函数,仅使用指定类型。 请注意与旧解析程序(如果存在)的合并。

泛型解析程序特性

如果设计的服务需要泛型解析程序,最好不要受主机控制,在设计时就声明需要泛型解析程序。 为此,我编写了 GenericResolverBehaviorAttribute:

 
[AttributeUsage(AttributeTargets.Class)]
public class GenericResolverBehaviorAttribute :
Attribute,IServiceBehavior { void IServiceBehavior.Validate(
ServiceDescription serviceDescription,
ServiceHostBase serviceHostBase) { ServiceHost host = serviceHostBase as ServiceHost;
host.AddGenericResolver();
}
// More members
}

这一简洁的特性使服务独立于主机:

 

GenericResolverBehaviorAttribute 派生自 IServiceBehavior,后者是特殊的 WCF 接口,也是 WCF 中最常使用的扩展。 当主机加载服务时,主机会调用 IServiceBehavior 方法(具体地说就是 Validate 方法),这样,该特性可以与主机交互。 对于 GenericResolverBehaviorAttribute,它会将泛型解析程序添加到主机。

至此您已实现了您的目的:通过相对简单灵活的方式避免数据协定继承的麻烦。 您可以在下一个 WCF 项目中采用这种方法。

 

Juval Lowy 是 IDesign 的一名软件架构师,该公司提供 .NET 和体系结构培训和咨询服务。本文节选自他最近撰写的《Programming WCF Services 3rd Edition》一书(O'Reilly,2010 年)。另外,他也是 Microsoft 硅谷地区的区域总监。您可以通过 idesign.net 与 Lowy 联系。

衷心感谢以下技术专家对本文的审阅:Glenn Block 和 Amadeo Casas Cuadrado

WCF 已知类型和泛型解析程序 KnownType的更多相关文章

  1. wcf已知类型 known type

    .服务契约的定义 /* Copyright (c) 2014 HaiHui Software Co., Ltd. All rights reserved * * Create by huanglc@h ...

  2. WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

    原文:WCF技术剖析之十三:序列化过程中的已知类型(Known Type) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话) ...

  3. WCF数据契约代理和已知类型的使用

    using Bll; using System; using System.CodeDom; using System.Collections.Generic; using System.Collec ...

  4. WCF 之 已知类型(KnownType)

    已知类型(Known types)允许在服务契约中使用多态的行为,在服务操作中暴露基本类型.将已知类型(known types)相关到基本类型(基类类型)自身;特定操作;整个服务契约采用属性声明或者配 ...

  5. C# 序列化过程中的已知类型(Known Type)

    WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML.由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型.如果被序列化对象或者被反序 ...

  6. java基础 File与递归练习 使用文件过滤器筛选将指定文件夹下的小于200K的小文件获取并打印按层次打印(包括所有子文件夹的文件) 多层文件夹情况统计文件和文件夹的数量 统计已知类型的数量 未知类型的数量

    package com.swift.kuozhan; import java.io.File; import java.io.FileFilter; /*使用文件过滤器筛选将指定文件夹下的小于200K ...

  7. OpenCV开发笔记(六十九):红胖子8分钟带你使用传统方法识别已知物体(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  8. WCF中数据契约之已知类型的几种公开方式

    WCF中传输的数据不想传统的面向对象编程,它只传递了一些对象的属性,但是自身并不知道自己属于什么对象,所以,他没有子类和父类的概念,因而也就没有Is-a的关系,所以在WCF中,如果想维持这种继承关系, ...

  9. C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常,不存在从对象类型System.Windows.Forms.DateTimePicker到已知的托管提供程序本机类型的映射。

    一:C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常 其实,这个问题与C#的垃圾回收有关.垃圾回收器管 理所有的托管对象,所有需要托管数据的.NET语言(包括 C#)都受运行库的 垃圾回收器 ...

随机推荐

  1. jsp使用servlet实现验证码

    在进行表单设计中,验证码的增加恰恰可以实现是否为“人为”操作,增加验证码可以防止网站数据库信息的冗杂等... 现在,小编将讲述通过servlet实现验证码: 验证码作为一个图片,在页面中为“画”出来的 ...

  2. POJ3254Corn Fields(状态压缩DP入门)

    题目链接 题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻.问有多少种放牛方案(一 ...

  3. Raspberry Pi 3 FAQ --- connect automatically to 'mirrors.zju.edu.cn' when downloading and how to accelerate download

    modify the software source: The software source is a place where several free application for linux ...

  4. 深入理解css中position属性及z-index属性

    深入理解css中position属性及z-index属性 在网页设计中,position属性的使用是非常重要的.有时如果不能认识清楚这个属性,将会给我们带来很多意想不到的困难. position属性共 ...

  5. [NOIP2014] 解方程&加强版 (bzoj3751 & vijos1915)

    大概有$O(m)$,$O(n\sqrt{nm})$,$O(n\sqrt{m})$的3个算法,其中后2个可以过加强版.代码是算法3,注意bzoj的数据卡掉了小于20000的质数. #include< ...

  6. win7怎么显示隐藏文件夹

    1. 点击“组织”,再选择“文件夹和搜索选项”命令. 2. 接下来在打开的“文件夹选项”对话框中,单击“查看”,切换到“查看”选项卡中. 3. 然后在下面的“高级设置”区域,取消“隐藏受保护的操作系统 ...

  7. 怎样用命令行编译C#程序

    1. 把程序拷贝至记事本 2.另存为*cs格式 3.找到VS2015提供的命令提示 4.要把命令行指向程序所在的目录(可以有个小软件) 如果在其他的盘符,先敲D:,然后再cd 5.输入csc hell ...

  8. https 页面中引入 http 资源的解决方式

    相对协议 应用场景 浏览器默认是不允许在 https 里面引用 http 资源的,一般都会弹出提示框. 用户确认后才会继续加载,用户体验非常差. 而且如果在一个 https 页面里动态的引入 http ...

  9. WinForm------给GridControl添加搜索功能

    //按钮点击事件 private void Btn_Search_Click(object sender, EventArgs e) { //获取编辑框的值 string text = this.te ...

  10. git push to nas

    1 建nas目录 在nas的/volume1/git_repos目录下新建相关的目录,并在该目录下运行git init --bare cd /volume1/git_repos mkdir wifi_ ...