C #에서 명시 적 형식 변환을 사용하여 파생 클래스 참조에 기본 클래스 개체를 할당 할 수 있습니까?
나는 그것을 시도했고 런타임 오류가 발생합니다.
C #에서 명시 적 형식 변환을 사용하여 파생 클래스 참조에 기본 클래스 개체를 할당 할 수 있습니까?
나는 그것을 시도했고 런타임 오류가 발생합니다.
답변:
아니요. 파생 클래스에 대한 참조는 실제로 파생 클래스의 인스턴스 (또는 null)를 참조해야합니다. 그렇지 않으면 어떻게 작동 할 것으로 예상합니까?
예를 들면 :
object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?
기본 형식의 인스턴스를 파생 형식으로 변환하려면 적절한 파생 형식 인스턴스를 만드는 메서드를 작성하는 것이 좋습니다. 또는 상속 트리를 다시보고 처음부터이 작업을 수행 할 필요가 없도록 재 설계를 시도하십시오.
Derived
하지만 Derived
참조를 참조로 취급 할 수 있습니다 Base
.
Base
만들고 다른 하나는의 인스턴스를 만듭니다 Derived
. b
에서 재정의 된 가상 메서드를 호출 하면 인스턴스가있는 경우 동작 Derived
이 표시 됩니다. 그러나 Stack Overflow 주석 스레드의 세부 사항으로 들어가는 것은 실제로 적절하지 않습니다. 이것은 매우 기본적인 내용이므로 좋은 C # 책이나 자습서를 읽어야합니다. Derived
Derived
아니요, 파생 클래스 참조에 할당하는 것은 "기본 클래스는 파생 클래스를 완전히 대체 할 수 있습니다. 파생 클래스가 할 수있는 모든 작업을 수행 할 수 있습니다."라고 말하는 것과 같기 때문에 불가능합니다. 이는 일반적으로 파생 클래스가 제공하므로 사실이 아닙니다. 기본 클래스보다 더 많은 기능을 제공합니다 (적어도 상속의 개념입니다).
기본 클래스 개체를 매개 변수로 사용하여 값을 복사하는 파생 클래스에 생성자를 작성할 수 있습니다.
이 같은:
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
다른 많은 사람들이 대답했듯이 아니요.
기본 형식을 파생 형식으로 사용해야하는 경우에 다음 코드를 사용합니다. 예, 그것은 Liskov Substitution Principle (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>()
반사를 사용하기 때문에 "비싸다". 그에 따라 사용하십시오.
user-defined conversions to or from a base class are not allowed
나는 이것에 대한 이유를 보았지만 실망했다. 그것은 너무 재미했을로이 ... 허용 한 경우
if (type.BaseType != null)
Markus Knappen Johansson의 A 와 관련된 Statement를 추가했습니다 . 그 이유는 무엇입니까? 즉, MyBaseClass에서 파생되지 않은 유형 (또는 그 문제에 대한 모든 것)을 호출에 허용합니다. myDerivedObject에 할당하면 여전히 컴파일러 오류가 발생한다는 것을 알고 있지만 표현식으로 만 사용하면 컴파일되고 런타임에 "myBaseObject"에서 복사 된 데이터없이 myDerivedObject 만 생성됩니다. 나는 그것을위한 유스 케이스를 상상할 수 없다.
오늘은 동일한 문제에 직면하고 난 간단하고 발견 문제에 대한 빠른 솔루션을 사용 JsonConvert
.
var base = new BaseClass();
var json = JsonConvert.SerializeObject(base);
DerivedClass derived = JsonConvert.DeserializeObject<DerivedClass>(json);
여기 모두가 말했듯이, 그것은 직접적으로 가능하지 않습니다.
내가 선호하고 다소 깨끗한 방법은 AutoMapper 와 같은 Object Mapper를 사용하는 것 입니다 .
한 인스턴스에서 다른 인스턴스 (동일한 유형일 필요는 없음)로 속성을 자동으로 복사하는 작업을 수행합니다.
아니요, 불가능합니다.
ACBus가 기본 클래스 Bus의 파생 클래스 인 시나리오를 고려하십시오. ACBus에는 ACState라는 필드에서 작동하는 TurnOnAC 및 TurnOffAC와 같은 기능이 있습니다. TurnOnAC는 ACState를 켜기로 설정하고 TurnOffAC는 ACState를 끄기로 설정합니다. 버스에서 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 문자열로 역 직렬화하여 하위 클래스 개체를 만듭니다. 그러면 기본 클래스의 모든 속성을 사용하여 하위 클래스의 인스턴스가 생성됩니다.
이것은 매력처럼 작동합니다! 그래서 ..이게 언제 유용할까요? 어떤 사람들은 이것이 언제 이치에 맞는지 물었고 (.Net에서) 클래스 상속으로 기본적으로 이것을 할 수 없다는 사실을 수용하기 위해 OP의 스키마를 변경하도록 제안했습니다.
제 경우에는 서비스에 대한 모든 "기본"설정을 포함하는 설정 클래스가 있습니다. 특정 서비스에는 더 많은 옵션이 있으며 다른 DB 테이블에서 제공되므로 해당 클래스는 기본 클래스를 상속합니다. 그들은 모두 다른 옵션 세트를 가지고 있습니다. 따라서 서비스에 대한 데이터를 검색 할 때 기본 개체의 인스턴스를 사용하여 값을 먼저 채우는 것이 훨씬 쉽습니다. 단일 DB 쿼리로이를 수행하는 한 가지 방법입니다. 그 직후 위에서 설명한 방법을 사용하여 하위 클래스 개체를 만듭니다. 그런 다음 두 번째 쿼리를 만들고 하위 클래스 개체의 모든 동적 값을 채 웁니다.
최종 출력은 모든 옵션이 설정된 파생 클래스입니다. 추가 새 하위 클래스에 대해이 작업을 반복하면 몇 줄의 코드 만 필요합니다. 간단하고, 매우 검증 된 패키지 (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)
var destObject = JsonConvert.DeserializeObject<DestinationType>(JsonConvert.SerializeObject(srcObject));
. 나는 단위 테스트 및 기타 비 프로덕션 "해킹"에만 이것을 사용합니다!
확장을 사용할 수 있습니다.
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);
}
관련성이 없을 수도 있지만 기반이 주어진 파생 개체에서 코드를 실행할 수있었습니다. 내가 원하는 것보다 확실히 더 해키하지만 작동합니다.
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);
generic을 사용하여이 작업을 수행 할 수 있습니다.
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);
}
}
이전 답변의 일부를 결합하고 (저자 덕분에) 우리가 사용하는 두 가지 방법으로 간단한 정적 클래스를 구성했습니다.
예, 간단합니다. 아니요 모든 시나리오를 다룰 수는 없습니다. 예 확장되고 더 나아질 수 있습니다. 아니요 완벽하지 않습니다. 예, 더 효율적으로 만들 수 있습니다. 아니요 슬라이스 빵 이후로 가장 좋은 것은 아닙니다. 예 강력한 너겟 패키지 객체 매퍼는 과도하게 사용하는 등의 방법이 더 좋습니다. 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 # 설명서를 참조했습니다 .
또 다른 해결책은 다음과 같은 확장 방법을 추가하는 것입니다.
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);
}
}
또한 이미 설정되어 있거나 (null이 아님) 대상 속성을 선택적으로 덮어 씁니다.
C #에서 명시 적 형식 변환을 사용하여 파생 클래스 참조에 기본 클래스 개체를 할당 할 수 있습니까?
명시적일뿐만 아니라 암시 적 변환도 가능합니다.
C # 언어는 이러한 변환 연산자를 허용하지 않지만 여전히 순수 C #을 사용하여 작성할 수 있으며 작동합니다. 암시 적 변환 연산자 ( Derived
) 를 정의하는 클래스와 연산자 ( )를 사용 Program
하는 Derived
클래스는 별도의 어셈블리에서 정의해야합니다 (예 : 클래스가 클래스 library.dll
를 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
물결 선이 없습니다.
System.Runtime.CompilerServices.SpecialName
Attribute는 무엇을합니까? 사용 가능한 초기 버전 (2.0)에서 "현재 버전"(4.6? "anyone? anyone?")에 이르는 모든 버전에 대한 문서는 그것이 무엇을하는지 말하지 않고 "SpecialNameAttribute 클래스는 현재 .NET에서 사용되지 않습니다. 프레임 워크이지만 향후 사용을 위해 예약되어 있습니다. ". 참조 : [link] ( msdn.microsoft.com/en-us/library/ms146064(v=vs.100).aspx ).
where T : Delegate
: 인덱서 등의 매개 변수화 된 속성).
what does System.Runtime.CompilerServices.SpecialName Attribute do?
-속성 접근 자, 이벤트 접근 자, 생성자, 연산자, 인덱서 등 고급 .Net 언어의 특수한 편의 구조에 의해 생성 된 메서드를 표시하는 데 사용됩니다. IL 메서드가 표시되지 않는 한 표시 specialname
되지 않습니다. 속성 / 이벤트 / 생성자로서 일반적인 방법으로 인식됩니다. 이 속성을 사용하여 적절하게 이름이 지정된 메서드를 수동으로 표시하는 것은 컴파일러 작업의 일부를 수동으로 수행하는 것입니다.
op_Exponent
메소드를 정의 하고 specialname
속성으로 표시하기 만하면 됩니다.
파생 항목에 모든 기본 속성을 추가하는 가장 좋은 방법은 코스 트럭 터에서 리플렉션을 사용하는 것입니다. 메서드 나 인스턴스를 만들지 않고이 코드를 사용해보십시오.
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);
var 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으로 직렬화 한 다음 파생 된 개체로 역 직렬화 할 수 있습니다.
전통적인 감각이 아닙니다 ... Json으로 변환 한 다음 개체로 변환하고 붐, 완료! 위의 Jesse는 먼저 답변을 게시했지만 프로세스를 훨씬 쉽게 만드는 이러한 확장 방법을 사용하지 않았습니다. 몇 가지 확장 메서드를 만듭니다.
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에 의해 본질적으로 처리되지 않습니다. 그러나 우리 클래스가 private 필드와 생성자에 의존하지 않는다면, 이것은 생성자를 매핑하고 호출하지 않고 클래스에서 클래스로 데이터를 이동하는 매우 효과적인 방법이며, 이것이 우리가 처음에 캐스팅하려는 주된 이유입니다.
아니요, 제가 물어 본 질문을 참조하십시오- 제네릭을 사용하여 .NET에서 업 캐스팅
가장 좋은 방법은 클래스에 기본 생성자를 만들고 생성 한 다음 Initialise
메서드 를 호출하는 것입니다.