I'm creating component to generate form at run-time with Blazor and C# (NET6). I have an issue with list of interfaces. Let me explain.
I have an interface for all element called IElement
public interface IElement
{
public string? Type { get; set; }
public string? Name { get; set; }
}
Then, I have few classes that inherit from it, for example
public class Textbox : IElement
{
[JsonPropertyName("type")]
public virtual string? Type { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("text")]
public string? Text { get; set; }
}
public class Radiobutton : IElement
{
[JsonPropertyName("type")]
public virtual string? Type { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("choises")]
public List<string> Choises = new List<string>();
}
Now, I'm creating a component when I want to repeat multiple times the some 2 element (for example). So, I create a
List<IElement>? RepeaterElements { get; set; }
In this list I add 2 elements: one Textbox and one Radiobutton with some properties. I created a function to create at run-time an instance of the derived class.
void AddRow()
{
ElementData.NumberOfRows = 1;
row ;
int question = 0;
foreach (var el in ElementData.RepeaterElements)
{
question ;
var newInstance = el.GetType();
var instance = Activator.CreateInstance(newInstance) as IElement;
instance.Parent = ElementData.Name;
instance.Index = row;
instance.QuestionNumber = question.ToString();
instance.Name = instance.GetElementName();
RepeaterElements.Add(new RepeaterElement() { Element = instance, Row = row });
}
}
How can I have in the new instance all the property values?
I tried to add an object like
var instance = Activator.CreateInstance(newInstance, new object[] { el }) as IElement;
but it is not working because I get the following exception
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Constructor on type 'PSC.Survey.Shared.Checkbox' not found. System.MissingMethodException: Constructor on type 'PSC.Survey.Shared.Checkbox' not found. at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) at System.Activator.CreateInstance(Type type, Object[] args) at PSC.Blazor.Components.Survey.Components.Repeater.AddRow() in C:\Projects\FromEnricoRossiniDevOps\SurveyGenerator\PSC.Blazor.Components.Survey\Components\Repeater.razor.cs:line 55 at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync[Object](MulticastDelegate delegate, Object arg) at Microsoft.AspNetCore.Components.EventCallbackWorkItem.InvokeAsync(Object arg) at Microsoft.AspNetCore.Components.ComponentBase.Microsoft.AspNetCore.Components.IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, Object arg) at Microsoft.AspNetCore.Components.EventCallback.InvokeAsync(Object arg) at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
CodePudding user response:
Because the default constructor of the Checkbox
is replaced by other constructors. Based on the error you receive and the code your provided, I guess you are missing the default constructor (the empty one) in your Checkbox
class. To fix that, simply add one as below:
public class Checkbox: IElement
{
// Add this default Constructor
public Checkbox() {}
// Other existed constructor
// public Checkbox(Type1 arg1, Type2 arg2) {}
}
The reason why the Textbox
and Radiobutton
don't throw errors because you don't specify any custom constructor
for them, so the default constructor would be used.
If you don’t provide a constructor for your class, C# creates one by default that instantiates the object and sets member variables to the default values. A constructor without any parameters is called a default constructor
In your case, based on the documents of System.Activator.CreatetInstance - CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]). You are not passing any paremeter to instantite the Element, so it uses the Default Constructor
CodePudding user response:
The AddRow method doesn´t make sense to me. You seem to iterate RepeaterElements but inside the foreach statement you add new elements to the list.
Anyway, when working with interfaces you can have a list of different types (but all of them implementing the interface) like you did. And you can cast the elements of the list to the type you need to get all the properties and concrete methods. I Believe that the propety Type in IElement is just for this. Maybe something like:
if(el.Type == "Textbox") { var castedEl = el as Textbox; } // here you access all properties and methods of Textbox in te castedEl object.
Another thing you can use is the "is" operator:
if(el is Textbox) { ((Textbox)el).Text = ""; }