동적으로 클래스를 만드는 방법은 무엇입니까?


221

다음과 같은 수업이 있습니다.

public class Field
{
    public string FieldName;
    public string FieldType;
}

그리고 List<Field>값이 있는 객체 :

{"EmployeeID","int"},
{"EmployeeName","String"},
{"Designation","String"}

다음과 같은 클래스를 만들고 싶습니다.

Class DynamicClass
{
    int EmployeeID,
    String EmployeeName,
    String Designation
}

이것을 할 수있는 방법이 있습니까?

런타임에 이것을 생성하고 싶습니다. 파일 시스템에 실제 CS 파일이 있으면 안됩니다.


4
런타임에 해당 클래스를 사용 하시겠습니까, 아니면 파일 만 생성 하시겠습니까?
Damian Leszczyński-Vash

런타임에 이것을 생성하고 싶습니다. 파일 시스템에 실제 CS 파일이 있어야합니다. 앞서 언급하지 않아서 죄송합니다.
ashwnacharya

16
당신은 우리에게 당신이하려는 않는 무엇을 거친 아이디어를 줄 수 이 클래스를?
저스틴

3
@Justin은 예를 들어 런타임 해결 인터페이스를 구현합니다.
AgentFire

하나는 그것을 먹이를 수System.ServiceModel.ChannelFactory<MyDynamicInterface>
일리아 세메 노프

답변:


297

예, System.Reflection.Emit네임 스페이스를 사용할 수 있습니다 . 경험이 없다면 간단하지 않지만 확실히 가능합니다.

편집 : 이 코드는 결함이있을 수 있지만 일반적인 아이디어를 제공하고 목표를 향해 좋은 출발을 할 수 있기를 바랍니다.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace TypeBuilderNamespace
{
    public static class MyTypeBuilder
    {
        public static void CreateNewObject()
        {
            var myType = CompileResultType();
            var myObject = Activator.CreateInstance(myType);
        }
        public static Type CompileResultType()
        {
            TypeBuilder tb = GetTypeBuilder();
            ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

            // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
            foreach (var field in yourListOfFields)
                CreateProperty(tb, field.FieldName, field.FieldType);

            Type objectType = tb.CreateType();
            return objectType;
        }

        private static TypeBuilder GetTypeBuilder()
        {
            var typeSignature = "MyDynamicType";
            var an = new AssemblyName(typeSignature);
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
                    TypeAttributes.Public |
                    TypeAttributes.Class |
                    TypeAttributes.AutoClass |
                    TypeAttributes.AnsiClass |
                    TypeAttributes.BeforeFieldInit |
                    TypeAttributes.AutoLayout,
                    null);
            return tb;
        }

        private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

            PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();

            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);

            MethodBuilder setPropMthdBldr =
                tb.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });

            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();

            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);

            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);

            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
}

2
대박!! CompileResultType () Methods에 의해 반환 된 타입의 객체를 생성하는 방법도 알려줄 수 있습니까?
ashwnacharya

4
이를 위해 System.Activator를 사용할 수 있습니다. 예를 들어 답변을 업데이트하겠습니다.
danijels

4
동적 유형의 필드를 검사, 읽기 및 업데이트하려면 리플렉션을 사용해야합니다. 인텔리전스를 원하고 리플렉션을 원하지 않으면 동적 클래스가 상속하고 캐스트 할 수있는 정적 기본 클래스 또는 인터페이스가 있어야합니다. 이 경우 GetTypeBuilder () 메서드를 수정하고 마지막 유형으로 정적 유형을 포함하도록 moduleBuilder.DefineType 호출을 변경할 수 있습니다 (지금 null 임)
danijels

2
누군가는 생성 후 객체를 사용하는 방법을 설명 할 수
HELP_ME

3
@bugz는 위의 코드를 사용하여 클래스를 만든 다음 기본 클래스에서 다음 메소드를 추가 할 수 있습니다. public void SetValue <T> (string name, T value) {GetType (). GetProperty (name) .SetValue (this, value ); }
엄격한

71

약간의 작업이 필요하지만 확실히 불가능하지는 않습니다.

내가 한 일은 :

  • 문자열로 C # 소스를 만듭니다 (파일에 쓸 필요가 없음).
  • Microsoft.CSharp.CSharpCodeProvider(CompileAssemblyFromSource)를 통해 실행하십시오.
  • 생성 된 유형 찾기
  • 그리고 해당 유형의 인스턴스를 만듭니다 ( Activator.CreateInstance).

이 방법으로 MSIL을 방출하지 않고 이미 알고있는 C # 코드를 처리 할 수 ​​있습니다.

그러나 이것은 클래스가 일부 인터페이스를 구현하거나 일부 기본 클래스에서 파생 된 경우 가장 잘 작동합니다. 그렇지 않으면 호출 코드 (읽기 : 컴파일러)가 런타임에 생성 될 클래스에 대해 어떻게 알 수 있습니까?


7
이 토론을보고 싶을 수도 있습니다 : reflection-emit-vs-codedom
nawfal

1
ashwnacharya 인 @nawfal은 런타임에 원래 목록에 포함 된 멤버로 클래스를 동적으로 생성하려고했습니다. 런타임에 생성 된 파일에도 파일을 넣는 것이 좋은 성능 솔루션이라고 생각하지 않습니다.
sodjsn26fr

38

DynamicObject 를 사용하여 클래스를 동적으로 만들 수도 있습니다 .

public class DynamicClass : DynamicObject
{
    private Dictionary<string, KeyValuePair<Type, object>> _fields;

    public DynamicClass(List<Field> fields)
    {
        _fields = new Dictionary<string, KeyValuePair<Type, object>>();
        fields.ForEach(x => _fields.Add(x.FieldName,
            new KeyValuePair<Type, object>(x.FieldType, null)));
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (_fields.ContainsKey(binder.Name))
        {
            var type = _fields[binder.Name].Key;
            if (value.GetType() == type)
            {
                _fields[binder.Name] = new KeyValuePair<Type, object>(type, value);
                return true;
            }
            else throw new Exception("Value " + value + " is not of type " + type.Name);
        }
        return false;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = _fields[binder.Name].Value;
        return true;
    }
}

모든 클래스 필드를 _fields유형과 값과 함께 사전에 저장 합니다. 두 가지 방법 모두 일부 속성 값을 가져 오거나 설정할 수 있습니다. dynamic이 클래스의 인스턴스를 작성 하려면 키워드를 사용해야합니다 .

귀하의 예와 사용법 :

var fields = new List<Field>() { 
    new Field("EmployeeID", typeof(int)),
    new Field("EmployeeName", typeof(string)),
    new Field("Designation", typeof(string)) 
};

dynamic obj = new DynamicClass(fields);

//set
obj.EmployeeID = 123456;
obj.EmployeeName = "John";
obj.Designation = "Tech Lead";

obj.Age = 25;             //Exception: DynamicClass does not contain a definition for 'Age'
obj.EmployeeName = 666;   //Exception: Value 666 is not of type String

//get
Console.WriteLine(obj.EmployeeID);     //123456
Console.WriteLine(obj.EmployeeName);   //John
Console.WriteLine(obj.Designation);    //Tech Lead

편집 : 그리고 내 수업 모습은 다음 과 같습니다 Field.

public class Field
{
    public Field(string name, Type type)
    {
        this.FieldName = name;
        this.FieldType = type;
    }

    public string FieldName;

    public Type FieldType;
}

1
생성자로 필드를 초기화해야 할 때까지이 방법이 마음에 들었습니다. 즉, dynamic obj = new DynamicClass(fields){EmployeeId=123456;EmployeeName = "John"; Designation = "Tech Lead";}이 작업을 수행하는 것이 정말 좋습니다.
rey_coder

14

나는이 오래된 작업을 다시 열었지만 c # 4.0을 사용하면이 작업이 절대적으로 고통스럽지 않습니다.

dynamic expando = new ExpandoObject();
expando.EmployeeID=42;
expando.Designation="unknown";
expando.EmployeeName="curt"

//or more dynamic
AddProperty(expando, "Language", "English");

자세한 내용은 https://www.oreilly.com/learning/building-c-objects-dynamically를 참조 하십시오


예, 그러나 여기서는 유형 안전이 손실됩니다. 타입 안전을 유지하면서 비슷한 것을 할 수 있습니까?
toughQuestions

13

그런 동적 클래스의 의도 된 사용법을 모르고 코드 생성 및 런타임 컴파일을 수행 할 수 있지만 약간의 노력이 필요합니다. 어쩌면 익명 유형 이 도움이 될 것입니다.

var v = new { EmployeeID = 108, EmployeeName = "John Doe" };

7
필드 이름을 하드 코딩 할 수 없습니다. 그는 그들에게 그의을 제공하고 있습니다 Field.FieldName. 필드 이름을 하드 코딩해야하는 경우 목적이 무효화됩니다. 그렇게해야 할 경우 클래스를 만들 수도 있습니다.
toddmo

9

CodeDOM 을보고 싶습니다 . 코드 요소를 정의하고 컴파일 할 수 있습니다. 인용 MSDN :

...이 객체 그래프는 지원되는 프로그래밍 언어에 대한 CodeDOM 코드 생성기를 사용하여 소스 코드로 렌더링 할 수 있습니다. CodeDOM을 사용하여 소스 코드를 이진 어셈블리로 컴파일 할 수도 있습니다.


런타임에 이것을 생성하고 싶습니다. 파일 시스템에 실제 CS 파일이 있어야합니다. 앞서 언급하지 않아서 죄송합니다.
ashwnacharya

1
@ashwnacharya : CodeDOM을 사용하여 소스 파일을 생성하고 런타임에 컴파일 할 수 있습니다 !
Hemant

1
그러나 CodeDOM 컴파일러는 원시 문자열을 사용하므로 XSS 및 SQL 삽입에 사용되는 것과 유사한 "코드 삽입 공격"을 고려할 수 있습니다.
교체

6

@danijels의 답변에 따라 VB.NET에서 동적으로 클래스를 만듭니다.

Imports System.Reflection
Imports System.Reflection.Emit

Public Class ObjectBuilder

Public Property myType As Object
Public Property myObject As Object

Public Sub New(fields As List(Of Field))
    myType = CompileResultType(fields)
    myObject = Activator.CreateInstance(myType)
End Sub

Public Shared Function CompileResultType(fields As List(Of Field)) As Type
    Dim tb As TypeBuilder = GetTypeBuilder()
    Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)

    For Each field In fields
        CreateProperty(tb, field.Name, field.Type)
    Next

    Dim objectType As Type = tb.CreateType()
    Return objectType
End Function

Private Shared Function GetTypeBuilder() As TypeBuilder
    Dim typeSignature = "MyDynamicType"
    Dim an = New AssemblyName(typeSignature)
    Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)
    Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")
    Dim tb As TypeBuilder = moduleBuilder.DefineType(typeSignature, TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, Nothing)
    Return tb
End Function

Private Shared Sub CreateProperty(tb As TypeBuilder, propertyName As String, propertyType As Type)
    Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private])

    Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)
    Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)
    Dim getIl As ILGenerator = getPropMthdBldr.GetILGenerator()

    getIl.Emit(OpCodes.Ldarg_0)
    getIl.Emit(OpCodes.Ldfld, fieldBuilder)
    getIl.Emit(OpCodes.Ret)

    Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, {propertyType})

    Dim setIl As ILGenerator = setPropMthdBldr.GetILGenerator()
    Dim modifyProperty As Label = setIl.DefineLabel()
    Dim exitSet As Label = setIl.DefineLabel()

    setIl.MarkLabel(modifyProperty)
    setIl.Emit(OpCodes.Ldarg_0)
    setIl.Emit(OpCodes.Ldarg_1)
    setIl.Emit(OpCodes.Stfld, fieldBuilder)

    setIl.Emit(OpCodes.Nop)
    setIl.MarkLabel(exitSet)
    setIl.Emit(OpCodes.Ret)

    propertyBuilder.SetGetMethod(getPropMthdBldr)
    propertyBuilder.SetSetMethod(setPropMthdBldr)
End Sub

End Class

6

DynamicExpressions 를 사용하여 클래스를 동적으로 만들 수도 있습니다. .

'사전'에는 컴팩트 한 이니셜 라이저가 있고 키 충돌을 처리하므로 이와 같은 작업을 수행해야합니다.

  var list = new Dictionary<string, string> {
    {
      "EmployeeID",
      "int"
    }, {
      "EmployeeName",
      "String"
    }, {
      "Birthday",
      "DateTime"
    }
  };

또는 JSON 변환기를 사용하여 직렬화 된 문자열 객체를 관리 가능한 것으로 구성 할 수 있습니다.

그런 다음 System.Linq.Dynamic을 사용하십시오.

  IEnumerable<DynamicProperty> props = list.Select(property => new DynamicProperty(property.Key, Type.GetType(property.Value))).ToList();

  Type t = DynamicExpression.CreateClass(props);

나머지는 System.Reflection을 사용하고 있습니다.

  object obj = Activator.CreateInstance(t);
  t.GetProperty("EmployeeID").SetValue(obj, 34, null);
  t.GetProperty("EmployeeName").SetValue(obj, "Albert", null);
  t.GetProperty("Birthday").SetValue(obj, new DateTime(1976, 3, 14), null);
}  

4

동적 클래스 만 속성 (예 : POCO)을 작성하고이 클래스의 목록을 작성하려는 경우. 나중에 제공된 코드를 사용하여 동적 클래스를 작성하고이 목록을 작성합니다.

var properties = new List<DynamicTypeProperty>()
{
    new DynamicTypeProperty("doubleProperty", typeof(double)),
    new DynamicTypeProperty("stringProperty", typeof(string))
};

// create the new type
var dynamicType = DynamicType.CreateDynamicType(properties);
// create a list of the new type
var dynamicList = DynamicType.CreateDynamicList(dynamicType);

// get an action that will add to the list
var addAction = DynamicType.GetAddAction(dynamicList);

// call the action, with an object[] containing parameters in exact order added
addAction.Invoke(new object[] {1.1, "item1"});
addAction.Invoke(new object[] {2.1, "item2"});
addAction.Invoke(new object[] {3.1, "item3"});

이전 코드에서 사용하는 클래스는 다음과 같습니다.

참고 : Microsoft.CodeAnalysis.CSharp 라이브러리도 참조해야합니다.

       /// <summary>
    /// A property name, and type used to generate a property in the dynamic class.
    /// </summary>
    public class DynamicTypeProperty
    {
        public DynamicTypeProperty(string name, Type type)
        {
            Name = name;
            Type = type;
        }
        public string Name { get; set; }
        public Type Type { get; set; }
    }

   public static class DynamicType
    {
        /// <summary>
        /// Creates a list of the specified type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<object> CreateDynamicList(Type type)
        {
            var listType = typeof(List<>);
            var dynamicListType = listType.MakeGenericType(type);
            return (IEnumerable<object>) Activator.CreateInstance(dynamicListType);
        }

        /// <summary>
        /// creates an action which can be used to add items to the list
        /// </summary>
        /// <param name="listType"></param>
        /// <returns></returns>
        public static Action<object[]> GetAddAction(IEnumerable<object> list)
        {
            var listType = list.GetType();
            var addMethod = listType.GetMethod("Add");
            var itemType = listType.GenericTypeArguments[0];
            var itemProperties = itemType.GetProperties();

            var action = new Action<object[]>((values) =>
            {
                var item = Activator.CreateInstance(itemType);

                for(var i = 0; i < values.Length; i++)
                {
                    itemProperties[i].SetValue(item, values[i]);
                }

                addMethod.Invoke(list, new []{item});
            });

            return action;
        }

        /// <summary>
        /// Creates a type based on the property/type values specified in the properties
        /// </summary>
        /// <param name="properties"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Type CreateDynamicType(IEnumerable<DynamicTypeProperty> properties)
        {
            StringBuilder classCode = new StringBuilder();

            // Generate the class code
            classCode.AppendLine("using System;");
            classCode.AppendLine("namespace Dexih {");
            classCode.AppendLine("public class DynamicClass {");

            foreach (var property in properties)
            {
                classCode.AppendLine($"public {property.Type.Name} {property.Name} {{get; set; }}");
            }
            classCode.AppendLine("}");
            classCode.AppendLine("}");

            var syntaxTree = CSharpSyntaxTree.ParseText(classCode.ToString());

            var references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
                MetadataReference.CreateFromFile(typeof(DictionaryBase).GetTypeInfo().Assembly.Location)
            };

            var compilation = CSharpCompilation.Create("DynamicClass" + Guid.NewGuid() + ".dll",
                syntaxTrees: new[] {syntaxTree},
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);

                if (!result.Success)
                {
                    var failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    var message = new StringBuilder();

                    foreach (var diagnostic in failures)
                    {
                        message.AppendFormat("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }

                    throw new Exception($"Invalid property definition: {message}.");
                }
                else
                {

                    ms.Seek(0, SeekOrigin.Begin);
                    var assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);
                    var dynamicType = assembly.GetType("Dexih.DynamicClass");
                    return dynamicType;
                }
            }
        }
    }

훌륭한 코드입니다. 동적 목록에서 AddRange를 사용하여 한 번에 여러 레코드를 추가 할 수 있습니까?
RickyTad

2

작업을 수행 할 수있는 동적 모듈 및 클래스 사용을 볼 수 있습니다. 유일한 단점은 앱 도메인에 계속로드되어 있다는 것입니다. 그러나 .NET 프레임 워크 버전을 사용하면 변경 될 수 있습니다. .NET 4.0은 수집 가능한 동적 어셈블리를 지원하므로 클래스 / 유형을 동적으로 다시 만들 수 있습니다.


2

와! 그 답변에 감사드립니다! 나는 당신과 공유하는 "datatable to json"변환기를 만들기 위해 몇 가지 기능을 추가했다.

    Public Shared Sub dt2json(ByVal _dt As DataTable, ByVal _sb As StringBuilder)
    Dim t As System.Type

    Dim oList(_dt.Rows.Count - 1) As Object
    Dim jss As New JavaScriptSerializer()
    Dim i As Integer = 0

    t = CompileResultType(_dt)

    For Each dr As DataRow In _dt.Rows
        Dim o As Object = Activator.CreateInstance(t)

        For Each col As DataColumn In _dt.Columns
            setvalue(o, col.ColumnName, dr.Item(col.ColumnName))
        Next

        oList(i) = o
        i += 1
    Next

    jss = New JavaScriptSerializer()
    jss.Serialize(oList, _sb)


End Sub

그리고 "compileresulttype"하위에서 다음과 같이 변경했습니다.

    For Each column As DataColumn In _dt.Columns
        CreateProperty(tb, column.ColumnName, column.DataType)
    Next


Private Shared Sub setvalue(ByVal _obj As Object, ByVal _propName As String, ByVal _propValue As Object)
    Dim pi As PropertyInfo
    pi = _obj.GetType.GetProperty(_propName)
    If pi IsNot Nothing AndAlso pi.CanWrite Then
        If _propValue IsNot DBNull.Value Then
            pi.SetValue(_obj, _propValue, Nothing)

        Else
            Select Case pi.PropertyType.ToString
                Case "System.String"
                    pi.SetValue(_obj, String.Empty, Nothing)
                Case Else
                    'let the serialiser use javascript "null" value.
            End Select

        End If
    End If

End Sub


-1

Runtime Code Generation with JVM and CLR -피터 세 스토 프트

이 유형의 프로그래밍에 정말로 관심이있는 사람들을 위해 일하십시오.

당신을위한 나의 팁은 무언가를 선언하면 문자열을 피하려고 시도하므로 Field 클래스가있는 경우 System.Type 클래스를 사용 하여 문자열보다 필드 유형을 저장하는 것이 좋습니다 . 그리고 새로운 클래스를 만드는 대신 최상의 솔루션을 위해 새로운 클래스 대신 FiledInfo 로 작성된 클래스를 사용하십시오 .


2
링크가 죽었 : -1
글렌 Slayden

: 글렌 : 빠른 인터넷 검색 작업 링크를 밝혀 pdfs.semanticscholar.org/326a/...
안드레아스 Pardeike

@AndreasPardeike 감사합니다. 기사에서 수리했습니다.
Glenn Slayden
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.