是否可以在 C# 中使用显式类型转换将基类对象分配给派生类引用?
我已经尝试过了,它会产生运行时错误。
不可以。对派生类的引用必须实际引用派生类的一个实例(或 null)。否则你会如何期望它的行为?
例如:
object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?
如果您希望能够将基类型的实例转换为派生类型,我建议您编写一个方法来创建适当的派生类型实例。或者再次查看您的继承树并尝试重新设计,以便您一开始就不需要这样做。
不,这是不可能的,因为将其分配给派生类引用就像说“基类是派生类的完全替代品,它可以做派生类可以做的一切”,这是不正确的,因为派生类通常提供比它们的基类更多的功能(至少,这是继承背后的想法)。
您可以在派生类中编写一个构造函数,将基类对象作为参数,复制值。
像这样的东西:
public class Base {
public int Data;
public void DoStuff() {
// Do stuff with data
}
}
public class Derived : Base {
public int OtherData;
public Derived(Base b) {
this.Data = b.Data;
OtherData = 0; // default value
}
public void DoOtherStuff() {
// Do some other stuff
}
}
在这种情况下,您将复制基对象并获得具有派生成员默认值的功能齐全的派生类对象。这样你也可以避免 Jon Skeet 指出的问题:
Base b = new Base();//base class
Derived d = new Derived();//derived class
b.DoStuff(); // OK
d.DoStuff(); // Also OK
b.DoOtherStuff(); // Won't work!
d.DoOtherStuff(); // OK
d = new Derived(b); // Copy construct a Derived with values of b
d.DoOtherStuff(); // Now works!
我遇到了这个问题,并通过添加一个采用类型参数并将当前对象转换为该类型的方法来解决它。
public TA As<TA>() where TA : Base
{
var type = typeof (TA);
var instance = Activator.CreateInstance(type);
PropertyInfo[] properties = type.GetProperties();
foreach (var property in properties)
{
property.SetValue(instance, property.GetValue(this, null), null);
}
return (TA)instance;
}
这意味着您可以像这样在代码中使用它:
var base = new Base();
base.Data = 1;
var derived = base.As<Derived>();
Console.Write(derived.Data); // Would output 1
今天我遇到了同样的问题,我找到了一个简单快捷的解决方案JsonConvert
。
var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
正如许多其他人回答的那样,不。
当我需要将基类型用作派生类型时,我会在那些不幸的情况下使用以下代码。是的,这违反了 Liskov 替换原则 (LSP),是的,大多数时候我们更喜欢组合而不是继承。向 Markus Knappen Johansson 的道具,其原始答案基于此。
基类中的这段代码:
public T As<T>()
{
var type = typeof(T);
var instance = Activator.CreateInstance(type);
if (type.BaseType != null)
{
var properties = type.BaseType.GetProperties();
foreach (var property in properties)
if (property.CanWrite)
property.SetValue(instance, property.GetValue(this, null), null);
}
return (T) instance;
}
允许:
derivedObject = baseObect.As<derivedType>()
由于它使用反射,它是“昂贵的”。相应地使用。
不,这是不可能的,因此您的运行时错误。
但是您可以将派生类的实例分配给基类类型的变量。
扩展@ybo 的答案-这是不可能的,因为您拥有的基类实例实际上并不是派生类的实例。它只知道基类的成员,对派生类的成员一无所知。
您可以将派生类的实例强制转换为基类的实例的原因是派生类实际上已经是基类的实例,因为它已经具有这些成员。不能说相反的。
您可以将类型为基类的变量强制转换为派生类的类型;但是,这必然会进行运行时检查,以查看所涉及的实际对象是否属于正确的类型。
一旦创建,对象的类型就无法更改(尤其是,它的大小可能不同)。但是,您可以转换实例,创建第二种类型的新实例 - 但您需要手动编写转换代码。
您必须使用对象克隆器/复制器来一一分配所有属性。
手动执行此操作效率低下,而且不会面向未来。但是序列化和反序列化到 JSON 并返回不是最好的解决方案,它很慢而且内存效率很低。
但是,使用AutoMapper
速度很快。PropMapper
甚至更快。
PS。披露:我是PropMapper开源项目的贡献者。
不,这是不可能的。
考虑这样一个场景,其中 ACBus 是基类 Bus 的派生类。ACBus 具有 TurnOnAC 和 TurnOffAC 等功能,它们在名为 ACState 的字段上运行。TurnOnAC 将 ACState 设置为 on, TurnOffAC 将 ACState 设置为 off。如果您尝试在 Bus 上使用 TurnOnAC 和 TurnOffAC 功能,则毫无意义。
class Program
{
static void Main(string[] args)
{
a a1 = new b();
a1.print();
}
}
class a
{
public a()
{
Console.WriteLine("base class object initiated");
}
public void print()
{
Console.WriteLine("base");
}
}
class b:a
{
public b()
{
Console.WriteLine("child class object");
}
public void print1()
{
Console.WriteLine("derived");
}
}
}
当我们创建一个子类对象时,基类对象是自动启动的,因此基类引用变量可以指向子类对象。
但反之亦然,因为子类引用变量不能指向基类对象,因为没有创建子类对象。
还要注意基类引用变量只能调用基类成员。
实际上有一种方法可以做到这一点。考虑一下如何使用 Newtonsoft JSON 从 json 反序列化对象。它将(或至少可以)忽略缺失的元素并填充它确实知道的所有元素。
这就是我的做法。一个小的代码示例将遵循我的解释。
从基类创建对象的实例并相应地填充它。
使用 Newtonsoft json 的“jsonconvert”类,将该对象序列化为 json 字符串。
现在通过反序列化步骤 2 中创建的 json 字符串来创建子类对象。这将创建具有基类所有属性的子类实例。
这就像一个魅力!所以..这什么时候有用?有些人问这什么时候有意义,并建议更改 OP 的架构以适应您不能通过类继承(在 .Net 中)原生地做到这一点的事实。
就我而言,我有一个设置类,其中包含服务的所有“基本”设置。特定服务有更多选项,并且来自不同的数据库表,因此这些类继承了基类。他们都有一组不同的选项。因此,在检索服务的数据时,首先使用基础对象的实例填充值要容易得多。使用单个数据库查询执行此操作的一种方法。之后,我使用上述方法创建子类对象。然后我进行第二次查询并填充子类对象上的所有动态值。
最终输出是具有所有选项集的派生类。对其他新子类重复此操作只需要几行代码。这很简单,并且它使用了一个非常久经考验的软件包(Newtonsoft)来使魔法发挥作用。
此示例代码是 vb.Net,但您可以轻松转换为 c#。
' First, create the base settings object.
Dim basePMSettngs As gtmaPayMethodSettings = gtmaPayments.getBasePayMethodSetting(payTypeId, account_id)
Dim basePMSettingsJson As String = JsonConvert.SerializeObject(basePMSettngs, Formatting.Indented)
' Create a pmSettings object of this specific type of payment and inherit from the base class object
Dim pmSettings As gtmaPayMethodAimACHSettings = JsonConvert.DeserializeObject(Of gtmaPayMethodAimACHSettings)(basePMSettingsJson)
您可以使用扩展:
public static void CopyOnlyEqualProperties<T>(this T objDest, object objSource) where T : class
{
foreach (PropertyInfo propInfo in typeof(T).GetProperties())
if (objSource.GetType().GetProperties().Any(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()))
propInfo.SetValue(objDest, objSource.GetType().GetProperties().First(z => z.Name == propInfo.Name && z.GetType() == propInfo.GetType()).GetValue(objSource));
}
在代码中:
public class BaseClass
{
public string test{ get; set;}
}
public Derived : BaseClass
{
//Some properies
}
public void CopyProps()
{
BaseClass baseCl =new BaseClass();
baseCl.test="Hello";
Derived drv=new Derived();
drv.CopyOnlyEqualProperties(baseCl);
//Should return Hello to the console now in derived class.
Console.WriteLine(drv.test);
}
在 c# 9.0 中,您可以尝试为此使用记录。它们具有复制所有字段的默认复制构造函数 - 无需对所有字段使用反射/构造函数。
public record BaseR
{
public string Prop1 { get; set; }
}
public record DerivedR : BaseR
{
public DerivedR(BaseR baseR) : base(baseR) { }
public string Prop2 { get; set; }
}
var baseR = new BaseR { Prop1 = "base prob" };
var derivedR = new DerivedR(baseR) { Prop2 = "new prop" };
可能无关紧要,但我能够在给定其基础的派生对象上运行代码。它绝对比我想要的更hacky,但它有效:
public static T Cast<T>(object obj)
{
return (T)obj;
}
...
//Invoke parent object's json function
MethodInfo castMethod = this.GetType().GetMethod("Cast").MakeGenericMethod(baseObj.GetType());
object castedObject = castMethod.Invoke(null, new object[] { baseObj });
MethodInfo jsonMethod = baseObj.GetType ().GetMethod ("ToJSON");
return (string)jsonMethod.Invoke (castedObject,null);
您可以使用泛型来做到这一点。
public class BaseClass
{
public int A { get; set; }
public int B { get; set; }
private T ConvertTo<T>() where T : BaseClass, new()
{
return new T
{
A = A,
B = B
}
}
public DerivedClass1 ConvertToDerivedClass1()
{
return ConvertTo<DerivedClass1>();
}
public DerivedClass2 ConvertToDerivedClass2()
{
return ConvertTo<DerivedClass2>();
}
}
public class DerivedClass1 : BaseClass
{
public int C { get; set; }
}
public class DerivedClass2 : BaseClass
{
public int D { get; set; }
}
使用这种方法可以获得三个好处。
我知道这很旧,但我已经成功使用了一段时间。
private void PopulateDerivedFromBase<TB,TD>(TB baseclass,TD derivedclass)
{
//get our baseclass properties
var bprops = baseclass.GetType().GetProperties();
foreach (var bprop in bprops)
{
//get the corresponding property in the derived class
var dprop = derivedclass.GetType().GetProperty(bprop.Name);
//if the derived property exists and it's writable, set the value
if (dprop != null && dprop.CanWrite)
dprop.SetValue(derivedclass,bprop.GetValue(baseclass, null),null);
}
}
我结合了先前答案的某些部分(感谢那些作者),并将一个简单的静态类与我们正在使用的两种方法组合在一起。
是的,很简单,不,它不涵盖所有场景,是的,它可以扩展并变得更好,不,它并不完美,是的,它可能会变得更高效,不,它不是切片面包以来最棒的事情,是的全面强大的 nuget 包对象映射器,这些映射器更适合大量使用等,yada yada - 但它适用于我们的基本需求:)
当然,它会尝试将值从任何对象映射到任何对象,无论是否派生(当然只有名称相同的公共属性 - 忽略其余部分)。
用法:
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
// creates new object of type "RealPerson" and assigns any matching property
// values from the puppet object
// (this method requires that "RealPerson" have a parameterless constructor )
RealPerson person = ObjectMapper.MapToNewObject<RealPerson>(puppet);
// OR
// create the person object on our own
// (so RealPerson can have any constructor type that it wants)
SesameStreetCharacter puppet = new SesameStreetCharacter() { Name = "Elmo", Age = 5 };
RealPerson person = new RealPerson("tall") {Name = "Steve"};
// maps and overwrites any matching property values from
// the puppet object to the person object so now our person's age will get set to 5 and
// the name "Steve" will get overwritten with "Elmo" in this example
ObjectMapper.MapToExistingObject(puppet, person);
静态实用程序类:
public static class ObjectMapper
{
// the target object is created on the fly and the target type
// must have a parameterless constructor (either compiler-generated or explicit)
public static Ttarget MapToNewObject<Ttarget>(object sourceobject) where Ttarget : new()
{
// create an instance of the target class
Ttarget targetobject = (Ttarget)Activator.CreateInstance(typeof(Ttarget));
// map the source properties to the target object
MapToExistingObject(sourceobject, targetobject);
return targetobject;
}
// the target object is created beforehand and passed in
public static void MapToExistingObject(object sourceobject, object targetobject)
{
// get the list of properties available in source class
var sourceproperties = sourceobject.GetType().GetProperties().ToList();
// loop through source object properties
sourceproperties.ForEach(sourceproperty => {
var targetProp = targetobject.GetType().GetProperty(sourceproperty.Name);
// check whether that property is present in target class and is writeable
if (targetProp != null && targetProp.CanWrite)
{
// if present get the value and map it
var value = sourceobject.GetType().GetProperty(sourceproperty.Name).GetValue(sourceobject, null);
targetobject.GetType().GetProperty(sourceproperty.Name).SetValue(targetobject, value, null);
}
});
}
}
您可以使用立即调用实例构造函数的复制构造函数,或者如果您的实例构造函数执行的操作不止于赋值,则复制构造函数将传入值分配给实例。
class Person
{
// Copy constructor
public Person(Person previousPerson)
{
Name = previousPerson.Name;
Age = previousPerson.Age;
}
// Copy constructor calls the instance constructor.
public Person(Person previousPerson)
: this(previousPerson.Name, previousPerson.Age)
{
}
// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}
public int Age { get; set; }
public string Name { get; set; }
}
此示例在构造函数下引用了Microsoft C# 文档,过去曾遇到此问题。
不是传统意义上的...转换为 Json,然后转换为您的对象,然后繁荣,完成!上面的杰西首先发布了答案,但没有使用这些扩展方法,这使得过程变得如此简单。创建几个扩展方法:
public static string ConvertToJson<T>(this T obj)
{
return JsonConvert.SerializeObject(obj);
}
public static T ConvertToObject<T>(this string json)
{
if (string.IsNullOrEmpty(json))
{
return Activator.CreateInstance<T>();
}
return JsonConvert.DeserializeObject<T>(json);
}
将它们永远放在您的工具箱中,然后您可以随时执行此操作:
var derivedClass = baseClass.ConvertToJson().ConvertToObject<derivedClass>();
啊,JSON的力量。
这种方法有几个陷阱:我们实际上是在创建一个新对象,而不是强制转换,这可能很重要,也可能无关紧要。私有字段不会被传递,带参数的构造函数不会被调用等等。有可能一些子json不会被分配。JsonConvert 天生不处理流。但是,如果我们的类不依赖私有字段和构造函数,这是一种非常有效的将数据从一个类移动到另一个类的方法,无需映射和调用构造函数,这也是我们首先要进行强制转换的主要原因。
关于@MarkusKnappenJohansson 的回答和下面的评论,我们可以更改他的代码扩展扩展函数:) 因此它可以通过此代码更新现有的派生类实例:
public static TDerived As<TDerived>(this Base baseInstance, TDerived updateDerivedInstance = null) where TDerived : Base, new()
{
Type baseType = typeof(Base);
Type derivedType = typeof(TDerived);
PropertyInfo[] properties = baseType.GetProperties();
object instanceDerived = null;
if (updateDerivedInstance == null)
{
instanceDerived = Activator.CreateInstance(derivedType);
}
else
{
instanceDerived = (object)(updateDerivedInstance);
}
foreach (PropertyInfo property in properties)
{
if (property.CanWrite)
{
property.SetValue(instanceDerived, property.GetValue(baseInstance, null), null);
}
}
return (TDerived)instanceDerived;
}
获取新派生实例的用法是var base = new Base(); base.Data = 1; var derived = base.As<Derived>(); Console.Write(derived.Data); // Would output 1
用于更新现有派生实例的用法是var derived = new Derived(); var base = new Base(); base.Data = 1; var derivedUpdated = base.As<Derived>(derived); Console.Write(derivedUpdated.Data); // Would output 1
是否可以在 C# 中使用显式类型转换将基类对象分配给派生类引用?
不仅可以进行显式转换,还可以进行隐式转换。
C# 语言不允许此类转换运算符,但您仍然可以使用纯 C# 编写它们并且它们可以工作。请注意,定义隐式转换运算符 ( Derived
) 的类和使用运算符 ( Program
) 的类必须在单独的程序集中定义(例如,Derived
该类位于通过包含该类library.dll
引用的 a 中)。program.exe
Program
//In library.dll:
public class Base { }
public class Derived {
[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Implicit(Base a) {
return new Derived(a); //Write some Base -> Derived conversion code here
}
[System.Runtime.CompilerServices.SpecialName]
public static Derived op_Explicit(Base a) {
return new Derived(a); //Write some Base -> Derived conversion code here
}
}
//In program.exe:
class Program {
static void Main(string[] args) {
Derived z = new Base(); //Visual Studio can show squiggles here, but it compiles just fine.
}
}
当您使用 Visual Studio 中的项目引用引用该库时,VS 在您使用隐式转换时会显示曲线,但它编译得很好。如果您只引用library.dll
,则没有曲线。
另一种解决方案是添加扩展方法,如下所示:
public static void CopyProperties(this object destinationObject, object sourceObject, bool overwriteAll = true)
{
try
{
if (sourceObject != null)
{
PropertyInfo[] sourceProps = sourceObject.GetType().GetProperties();
List<string> sourcePropNames = sourceProps.Select(p => p.Name).ToList();
foreach (PropertyInfo pi in destinationObject.GetType().GetProperties())
{
if (sourcePropNames.Contains(pi.Name))
{
PropertyInfo sourceProp = sourceProps.First(srcProp => srcProp.Name == pi.Name);
if (sourceProp.PropertyType == pi.PropertyType)
if (overwriteAll || pi.GetValue(destinationObject, null) == null)
{
pi.SetValue(destinationObject, sourceProp.GetValue(sourceObject, null), null);
}
}
}
}
}
catch (ApplicationException ex)
{
throw;
}
}
然后在每个接受基类的派生类中都有一个构造函数:
public class DerivedClass: BaseClass
{
public DerivedClass(BaseClass baseModel)
{
this.CopyProperties(baseModel);
}
}
如果已设置(非空)或未设置,它还将可选地覆盖目标属性。
怎么样:
public static T As<T>(this object obj)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
}
将所有基本属性添加到派生项的最佳方法是在 costructor 中使用反射。试试这段代码,不要创建方法或实例。
public Derived(Base item) :base()
{
Type type = item.GetType();
System.Reflection.PropertyInfo[] properties = type.GetProperties();
foreach (var property in properties)
{
try
{
property.SetValue(this, property.GetValue(item, null), null);
}
catch (Exception) { }
}
}
我不同意这是不可能的。你可以这样做:
public class Auto
{
public string Make {get; set;}
public string Model {get; set;}
}
public class Sedan : Auto
{
public int NumberOfDoors {get; set;}
}
public static T ConvertAuto<T>(Sedan sedan) where T : class
{
object auto = sedan;
return (T)loc;
}
用法:
var sedan = new Sedan();
sedan.NumberOfDoors = 4;
var auto = ConvertAuto<Auto>(sedan);
这就是我为字段解决这个问题的方法。如果需要,您可以通过属性进行相同的迭代。您可能想要对null
等进行一些检查,但这就是我们的想法。
public static DerivedClass ConvertFromBaseToDerived<BaseClass, DerivedClass>(BaseClass baseClass)
where BaseClass : class, new()
where DerivedClass : class, BaseClass, new()
{
DerivedClass derived = (DerivedClass)Activator.CreateInstance(typeof(DerivedClass));
derived.GetType().GetFields().ToList().ForEach(field =>
{
var base_ = baseClass.GetType().GetField(field.Name).GetValue(baseClass);
field.SetValue(derived, base_);
});
return derived;
}
您可以将基础对象序列化为 JSON,然后将其反序列化为派生对象。
不,请参阅我提出的这个问题 - Upcasting in .NET using generics
最好的方法是在类上做一个默认构造函数,构造然后调用一个Initialise
方法