C
C#2w ago
stigzler

✅ Cannot convert List<MyClass> to List<object>

I'm trying to design a custom generic collection editor in Winforms. I'm getting into a bit of a mess with casting the received class type onto a list of objects. I'm not very good with generics, so tried this via List<object>. See my code here: https://pastebin.com/M2DwziWj Gives me the attached error (precis: Unable to cast object of type List<SupportApplications> to type List<object> I'm sure this may be a job for List<T> or something, but I've never really got my head around generics. I know this is me getting some basic principles wrong. Can anyone help with example code?
Pastebin
public class SupportApplications: object{ public string Name { get;...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
No description
37 Replies
jcotton42
jcotton422w ago
List is invariant, because otherwise you could do something like this
List<string> s = …;
List<object> o = s;
o.Add(1); // kaboom!
List<string> s = …;
List<object> o = s;
o.Add(1); // kaboom!
stigzler
stigzlerOP2w ago
Yes - that makes sense. How should I approach this then?
jcotton42
jcotton422w ago
You could make the form generic I guess?
stigzler
stigzlerOP2w ago
Like I say - I'm rubbish with generics. I tried this in GenericCollectionEditorForm:
public List<T> Objects { get; set; }
public List<T> Objects { get; set; }
but get the error "namespace T cannot be found" oh wait!
public partial class GenericCollectionEditorForm<T> : FormViewBase
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public List<T> Objects { get; set; }
public partial class GenericCollectionEditorForm<T> : FormViewBase
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public List<T> Objects { get; set; }
works¬ !
Angius
Angius2w ago
Yep, you need to get the T from somewhere for it to work
stigzler
stigzlerOP2w ago
how does one add a new object of type T to a list? Tried variations on the line below in GenericCollectionEditorForm<T> : FormViewBase , but no dice:
Objects.Add(<T>);
Objects.Add(<T>);
Also, Objects.Remove(objectsBindingSource.Current); doesn't compile - "cannot convert from object to T"
Angius
Angius2w ago
Just... add that object? If T is int, just add an int If T is Person, just add an instance of Person If T is IFoo then add an instance of any class that implements IFoo You get the idea
stigzler
stigzlerOP2w ago
but that defeats the point for the exercise - it's a generic collection editor - that is - GenericCollectionEditorForm won't know what type is being passed in - can you not achieve this via T?
Angius
Angius2w ago
Why won't it know the type? Just instantiate GenericCollectionEditorForm with a given type You have to, to even create the instance
jcotton42
jcotton422w ago
It sounds like they want to be able to make new Ts inside the editor?
stigzler
stigzlerOP2w ago
@Angius Code as currently stands:
public partial class GenericCollectionEditorForm<T> : FormViewBase
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public List<T> Objects { get; set; }

private Type type = null;

private BindingSource objectsBindingSource = new BindingSource();

public GenericCollectionEditorForm(List<T> objects, Type type)
{
//InitializeComponent();

Objects = objects;
this.type = type;

objectsBindingSource.DataSource = Objects;
MainLB.DataSource = objectsBindingSource;
}

private void AddBT_Click(object sender, EventArgs e)
{
if (type == null) return;
Objects.Add((T)Activator.CreateInstance(typeof(T), new object()));
RefreshEffectListDataBindings();
}

private void MainLB_SelectedValueChanged(object sender, EventArgs e)
{
MainPG.SelectedObject = MainLB.SelectedItem;
}

private void RefreshEffectListDataBindings()
{
MainLB.DataSource = null;
MainLB.DataSource = objectsBindingSource;
}

private void MainPG_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
//ComponentVM.UpdateDesignItemEffectProperties(MainPG.SelectedObject as Effect);
//ComponentVM.MainDatabase.Collections.Effects.Upsert(MainPG.SelectedObject as Effect);
}

private void DeleteBT_Click(object sender, EventArgs e)
{
if (MainLB.SelectedItems.Count == 0) return;
Objects.Remove(objectsBindingSource.Current);
RefreshEffectListDataBindings();
}

private void UpBT_Click(object sender, EventArgs e)
{
int newIndex = Math.Max(MainLB.SelectedIndex - 1, 0);
Objects.Move(MainLB.SelectedIndex, newIndex);
RefreshEffectListDataBindings();
MainLB.SelectedIndex = newIndex;
}

private void DownBT_Click(object sender, EventArgs e)
{
int newIndex = Math.Min(MainLB.SelectedIndex + 1, MainLB.Items.Count - 1);
Objects.Move(MainLB.SelectedIndex, newIndex);
RefreshEffectListDataBindings();
MainLB.SelectedIndex = newIndex;
}

}
public partial class GenericCollectionEditorForm<T> : FormViewBase
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public List<T> Objects { get; set; }

private Type type = null;

private BindingSource objectsBindingSource = new BindingSource();

public GenericCollectionEditorForm(List<T> objects, Type type)
{
//InitializeComponent();

Objects = objects;
this.type = type;

objectsBindingSource.DataSource = Objects;
MainLB.DataSource = objectsBindingSource;
}

private void AddBT_Click(object sender, EventArgs e)
{
if (type == null) return;
Objects.Add((T)Activator.CreateInstance(typeof(T), new object()));
RefreshEffectListDataBindings();
}

private void MainLB_SelectedValueChanged(object sender, EventArgs e)
{
MainPG.SelectedObject = MainLB.SelectedItem;
}

private void RefreshEffectListDataBindings()
{
MainLB.DataSource = null;
MainLB.DataSource = objectsBindingSource;
}

private void MainPG_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
//ComponentVM.UpdateDesignItemEffectProperties(MainPG.SelectedObject as Effect);
//ComponentVM.MainDatabase.Collections.Effects.Upsert(MainPG.SelectedObject as Effect);
}

private void DeleteBT_Click(object sender, EventArgs e)
{
if (MainLB.SelectedItems.Count == 0) return;
Objects.Remove(objectsBindingSource.Current);
RefreshEffectListDataBindings();
}

private void UpBT_Click(object sender, EventArgs e)
{
int newIndex = Math.Max(MainLB.SelectedIndex - 1, 0);
Objects.Move(MainLB.SelectedIndex, newIndex);
RefreshEffectListDataBindings();
MainLB.SelectedIndex = newIndex;
}

private void DownBT_Click(object sender, EventArgs e)
{
int newIndex = Math.Min(MainLB.SelectedIndex + 1, MainLB.Items.Count - 1);
Objects.Move(MainLB.SelectedIndex, newIndex);
RefreshEffectListDataBindings();
MainLB.SelectedIndex = newIndex;
}

}
Objects.Add((T)Activator.CreateInstance(typeof(T), new object())); untested at runtime as getting compile error on Objects.Remove(objectsBindingSource.Current); - "cannot convert from object to T"
jcotton42
jcotton422w ago
You can just cast.
stigzler
stigzlerOP2w ago
lol - oh ffs I'm dumb Now stuck on instantiating it. The variable type shows no errors, but cannot get the line below it right:
Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);
GenericCollectionEditorForm<T> form = new GenericCollectionEditorForm((List<type>)value);
Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);
GenericCollectionEditorForm<T> form = new GenericCollectionEditorForm((List<type>)value);
Angius
Angius2w ago
List<T>, assuming the type is the same as T You can't pass an instance of type as a generic parameter
stigzler
stigzlerOP2w ago
OK. Forget type. How would I instantiate GenericCollectionEditorForm? (have removed 'type' as a parameter):
public GenericCollectionEditorForm(List<T> objects)
{
Objects = objects;
// blah...
}

GenericCollectionEditorForm<T> form = new GenericCollectionEditorForm((List<T>)value);
public GenericCollectionEditorForm(List<T> objects)
{
Objects = objects;
// blah...
}

GenericCollectionEditorForm<T> form = new GenericCollectionEditorForm((List<T>)value);
Last line not working in many places!
Angius
Angius2w ago
Uh GenericCollectionEditorForm((T)value)...? We're firmly entering "weird shit" territory lol
stigzler
stigzlerOP2w ago
Sorry Zzzz - I'm a hobby coder - I know you're a super-duper expert coder and everything - so my amateurship may be throwing you tried: GenericCollectionEditorForm<Type> form = new GenericCollectionEditorForm((T)value);
stigzler
stigzlerOP2w ago
LHS worked, but RHS doesn't compile:
No description
Angius
Angius2w ago
GenericCollectionEditorForm<T> form = new GenericCollectionEditorForm((T)value); Or just var form = new GenericCollectionEditorForm((T)value); for simplicity
stigzler
stigzlerOP2w ago
naw - whichever permutation, it doens't like GenericCollectionEditorForm((T)value) it also doesn't like GenericCollectionEditorForm<T>!
Angius
Angius2w ago
Are we still inside of GenericCollectionEditorForm class, or somewhere else? Like, are you trying to create a new GenericCollectionEditorForm from another form?
stigzler
stigzlerOP2w ago
Yeah soz - now in GenericCollectionEditor which instantiates the form. It may be helpful for you to review my OP code - which shows the full structure.
Angius
Angius2w ago
Right, well, the generic type has to come from somewhere So you can either define it here,
var form = new GenericCollectionEditorForm<int>(69)
var form = new GenericCollectionEditorForm<int>(69)
Or it needs to come from... somewhere Be it a generic EditValue<T> or a generic GenericCollectionEditor<T>
stigzler
stigzlerOP2w ago
Let's focus the issue:
internal class GenericCollectionEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
if (context == null && context.Instance == null) return base.GetEditStyle(context);
return UITypeEditorEditStyle.Modal;
}

public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (context == null || provider == null || context.Instance == null) return value;

IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);

// What here??
GenericCollectionEditorForm<Type> form = new GenericCollectionEditorForm<Type>((List<Type>)value);

if (editorService.ShowDialog(form) == System.Windows.Forms.DialogResult.OK)
{
return form.Objects.ToList();
}

return base.EditValue(context, provider, value);
}
internal class GenericCollectionEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
if (context == null && context.Instance == null) return base.GetEditStyle(context);
return UITypeEditorEditStyle.Modal;
}

public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (context == null || provider == null || context.Instance == null) return value;

IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);

// What here??
GenericCollectionEditorForm<Type> form = new GenericCollectionEditorForm<Type>((List<Type>)value);

if (editorService.ShowDialog(form) == System.Windows.Forms.DialogResult.OK)
{
return form.Objects.ToList();
}

return base.EditValue(context, provider, value);
}
This line compiles, but erro at runtime: enericCollectionEditorForm<Type> form = new GenericCollectionEditorForm<Type>((List<Type>)value);
Angius
Angius2w ago
Well, you're creating an editor form for a list of Type instances Type instance is not the same as a generic
stigzler
stigzlerOP2w ago
yes I know! The question is again, how do I instantiate the form from what I recieve in the EditValue method? Are you familiar with UITypeEditor first of all?
Angius
Angius2w ago
The gist of it is: if you want to use generic types, the generic must be known at some point. You can't do
var foo = new Foo<whatever_lmao_idk>();
var foo = new Foo<whatever_lmao_idk>();
nor can you do
var t = typeof(69);
var foo = new Foo<t>();
var t = typeof(69);
var foo = new Foo<t>();
UITypeEditor
Honestly, no, first time I hear of it
stigzler
stigzlerOP2w ago
ah OK - well that may be part of the communicaiton break down. One moment
Angius
Angius2w ago
From what I can see in the docs (https://learn.microsoft.com/en-us/dotnet/api/system.drawing.design.uitypeeditor?view=windowsdesktop-9.0) it's not really generic I see a bunch of casting and typeofs So you might have to just forgo type-safety here and yolo it with typeofs, reflections, and objects Ancient .NET APIs were... not always well thought-out
stigzler
stigzlerOP2w ago
OK - so this may help you to help me (I do appreciate your efforts). Think property grid in Vis Studio. If you decorate a Property of an object with a UITypeEditor attribute, then when you edit it in the property grid, the class derived from UITypeEditor is called (you don't manually call it yourself). The value parameter of public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) holds the value of the property you want to edit. Thus this could be antyhning - a List, a string, an int etc. Have just tested my code. the line Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object); successfully detemrines the Type of value Now the remaining question is, how do I instantiate GenericCollectionEditorForm given I know the Type of value at runtime? Specifically - what is the syntax?
Angius
Angius2w ago
Jusr forgo the generics and pass object that is your value Pass the type intance as well if needed, sure
stigzler
stigzlerOP2w ago
no - that takes us right back to the very beginning again! See JCotton42's first comment!
Angius
Angius2w ago
But any generic types like lists will have to be List<object> or constructed with reflections
stigzler
stigzlerOP2w ago
nevermind - just leave it - maybe someone else can help
Angius
Angius2w ago
Well, if you have no generic type, you have no generic type The "source" only has an object instance You cannot, in any way shape or form, get a generic parameter out of it You could, maybe, cast all elements of the list to object in order to get the List<object> you need theList.Cast<object>() So that wherever it is the original error happens is happy
stigzler
stigzlerOP2w ago
Solved this for any that are in a similar boat. Found some Reflection voodoo that instantiated x from the determined Type:
Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);

Type genericClass = typeof(GenericCollectionEditorForm<>);
Type constructedClass = genericClass.MakeGenericType(type);
dynamic form = Activator.CreateInstance(constructedClass);

switch (type)
{
case Type t when t == typeof(SupportApplications):
List<SupportApplications> list = new List<SupportApplications>();
foreach (var item in (List<SupportApplications>)value) list.Add(item);
form.Objects = list;
break;
default:
form.Text = $"Edit {type.Name} Collection";
break;
}
Type type = value.GetType().GetGenericArguments().SingleOrDefault() ?? typeof(object);

Type genericClass = typeof(GenericCollectionEditorForm<>);
Type constructedClass = genericClass.MakeGenericType(type);
dynamic form = Activator.CreateInstance(constructedClass);

switch (type)
{
case Type t when t == typeof(SupportApplications):
List<SupportApplications> list = new List<SupportApplications>();
foreach (var item in (List<SupportApplications>)value) list.Add(item);
form.Objects = list;
break;
default:
form.Text = $"Edit {type.Name} Collection";
break;
}
Full code here: https://pastebin.com/gLHd2jqX
Pastebin
internal class GenericCollectionEditor : UITypeEditor { ...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
Anton
Anton2w ago
yes, you have to do that. or make factories, put them in a dictionary and look up by type, if you want more flexibility in how they are created idk why no one mentioned this the form might be doing type checks on the generic inside to select behavior, this might break

Did you find this page helpful?