Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misleading exception message for ctor argument names #719

Open
Ekkeir opened this issue Jun 21, 2024 · 5 comments
Open

Misleading exception message for ctor argument names #719

Ekkeir opened this issue Jun 21, 2024 · 5 comments

Comments

@Ekkeir
Copy link

Ekkeir commented Jun 21, 2024

There's an exception produced when mapping to a class with a ctor:

public class Destination
{
    public Destination(int number)
    {
        Id = number;
    }

    public int Id { get; }
}

public class Source
{
    public int Number { get; set; }
}

[Fact]
public void Should_Map()
{
    var config = new TypeAdapterConfig();

    config.ForType<Source, Destination>()
        .Map(dest => dest.Id, source => source.Number);

    config.Compile(); // throws an exception
}

The following exception is produced:

System.InvalidOperationException : No default constructor for type 'Destination', please use 'ConstructUsing' or 'MapWith'.

The exception message seems misleading as (I may have missed it in documentation) mapster seems to depend on argument names.
Renaming Destination ctor argument from number to id is enough for the test to succeed. That is, changing to the following:

public class Destination
{
    public Destination(int id)
    {
        Id = id;
    }

    public int Id { get; }
}

Suggestions:

  • maybe this name-dependency could be reduced?
  • it would be great if the exception message could mention that there's an option of renaming ctor arguments to match the properties
@stagep
Copy link

stagep commented Jul 4, 2024

What should happen in this scenario if name-dependency was reduced?

public class Destination
{
    public Destination(int number1, int number2)
    {
        Id1 = number2;
        Id2 = number1;
    }
    public int Id1 { get; }
    public int Id2 { get; }
}

@DocSvartz
Copy link

DocSvartz commented Jan 7, 2025

@stagep What version of mapster are you using?

By default, mapping to class class must always have a constructor without parameters.

After renaming
Your everything started to work because after the change your class started to be mistakenly defined as a record type. (v 7.4.0)

Use this if you need to map to a class constructor

TypeAdapterConfig<Source, Destination>
    .ForType()
    .Map(dest => dest.Id, source => source.Number)
    .MapToConstructor(true)
    .Compile();

@DocSvartz
Copy link

@andrerav Possible regression in the development branch. I don't remember if it was like that initially or not))

Properties without a setter are not updated for classes.

[TestMethod]
public void ProblemClassCtorfromIssue719()
{
    TypeAdapterConfig<Source719, Destination719>
        .ForType()
        .Map(dest => dest.Id, source => source.Number)
        .MapToConstructor(true)
        .Compile();

    var sourse = new Source719() { Number = 123 };
    var destination = new Destination719(202);

    var result = sourse.Adapt<Destination719>();
    var d = sourse.Adapt(destination); // d.Number (destination.number) == 202, not update  from sourse

}



  class Destination719
  {
     
      public Destination719(int id)
      {
          Id = id;
      }

      public int Id { get;} // if replace  to private set   update  it will work
  }

  class Source719
  {
      public int Number { get; set; }
  }

@andrerav
Copy link
Contributor

andrerav commented Jan 7, 2025

@DocSvartz Hm, that would be a read-only autoproperty. They can only be assigned in the constructor or as part of the declaration. So I think this is as expected.

Source: https://codeblog.jonskeet.uk/2014/12/08/c-6-in-action/

@DocSvartz
Copy link

DocSvartz commented Jan 9, 2025

@andrerav I also think that the behavior is consistent with the specification for this type of fields.

The behavior for class in 7.4 is similar. The field is not updated with the value from the Source

class Destination719
{

    public Destination719(int number)
    {
        Id = number;
    }
   
    public int Id { get; }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants